diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ce5fbf4..6c71bdd5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,18 +7,37 @@ workflows: version: 2 build-test-deploy: jobs: - - build-and-unit-test - - functional-test + - validate-code + - build-and-unit-test: + requires: + - validate-code + - functional-test: + requires: + - validate-code jobs: + validate-code: + working_directory: ~/code + docker: + - image: circleci/android:api-29-node + environment: + JVM_OPTS: -Xmx3200m + steps: + - checkout + + - run: + name: Check Code Format + command: make format-check + build-and-unit-test: working_directory: ~/code docker: - - image: circleci/android:api-29 + - image: circleci/android:api-29-node environment: JVM_OPTS: -Xmx3200m steps: - checkout + - run: name: Javadoc command: make ci-javadoc @@ -36,8 +55,26 @@ jobs: - run: name: UnitTests command: make ci-unit-test + + # code coverage + - run: + name: Upload Code Coverage Report + command: | + curl -s https://codecov.io/bash > codecov; + VERSION=$(grep 'VERSION=\"[0-9\.]*\"' codecov | cut -d'"' -f2); + SHAVERSION=$(shasum -v); + echo "Using CodeCov version '$VERSION'" + echo "Using shasum '$SHAVERSION'" + for i in 1 256 512 + do + shasum -a $i -c --ignore-missing <(curl -s "https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA${i}SUM") || + shasum -a $i -c <(curl -s "https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA${i}SUM" | grep -w "codecov") + done + bash ./codecov -v -X s3 -c -D "./ci/unit-test/build/reports" -F unit-tests + - store_artifacts: path: ci/unit-test/build/reports + - store_test_results: path: ci/unit-test/build/test-results @@ -55,8 +92,25 @@ jobs: # The test command test-command: make ci-functional-test + # code coverage + - run: + name: Upload Code Coverage Report + command: | + curl -s https://codecov.io/bash > codecov; + VERSION=$(grep 'VERSION=\"[0-9\.]*\"' codecov | cut -d'"' -f2); + SHAVERSION=$(shasum -v); + echo "Using CodeCov version '$VERSION'" + echo "Using shasum '$SHAVERSION'" + for i in 1 256 512 + do + shasum -a $i -c --ignore-missing <(curl -s "https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA${i}SUM") || + shasum -a $i -c <(curl -s "https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA${i}SUM" | grep -w "codecov") + done + bash ./codecov -v -X s3 -c -D "./ci/functional-test/build/reports" -F functional-tests + - store_artifacts: path: ci/functional-test/build/reports + - store_test_results: path: ci/functional-test/build/outputs/androidTest-results diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..e4a96e07 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/bin/bash + +make format +git diff --cached --name-only --diff-filter=d | while read filename; do git add "$filename"; done + diff --git a/.gitignore b/.gitignore index e06fcde8..f86dd9f4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ ci/ # IntelliJ *.iml **/.idea/ + +# Secrets +**/*/values/secrets.xml \ No newline at end of file diff --git a/Documentation/README.md b/Documentation/README.md new file mode 100644 index 00000000..5967d96f --- /dev/null +++ b/Documentation/README.md @@ -0,0 +1,53 @@ +# Advertising identifier + +## Configuration +To enable advertising identifier features in the sample app, follow these steps: +1. Update the value for key `gms_ads_app_id` located in the `secrets.xml` at [aepsdk-edgeidentity-android/code/app/src/main/res/values](../code/app/src/main/res/values/secrets.xml) with a valid Google AdMob app ID. + - See Google's [quick start reference](https://developers.google.com/admob/android/quick-start) on how to get your AdMob app ID. See step 3 of the [Configure your app](https://developers.google.com/admob/android/quick-start#import_the_mobile_ads_sdk) section for a free public test app ID from Google. + - Any real key values in the `secrets.xml` file should **not** be committed to the repository. +2. By default, the ad ID features are commented out in the sample app. To enable these features, uncomment the implemention code using [find and replace all](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-project.html#replace_search_string_in_project) to replace all instances of: +```java +/* Ad ID implementation +``` +with: +```java +//* Ad ID implementation +``` +Each code block has a pair of block comments wrapped around it to enable this behavior: +```java +/* Ad ID implementation (pt. 1/4) + +/* Ad ID implementation (pt. 1/4) */ +``` + +After replacement it will become: +```java +//* Ad ID implementation (pt. 1/4) + +//* Ad ID implementation (pt. 1/4) */ +``` + +For convenience, these are the default find and replace shortcuts in Android Studio: +[Default shortcuts for find and replace](./assets/find-and-replace-shortcuts.png) + +The shortcut should open a window that looks like the following: +[Example of find and replace](./assets/find-and-replace-all-example.png) +There should be 5 pairs of special comment blocks (10 total matches) across two files: +`app/build.gradle`, `CustomIdentityFragment.kt`, and `SharedViewModel.kt` + +3. With the implementation code and gradle files uncommented with new dependencies, sync the project with the Gradle file changes using: File -> Sync Project with Gradle Files + +[Example of find and replace](./assets/sync-project-gradle-example.png) + +The app should now be properly configured to use advertising identifier features. + +To **disable** these features, follow these steps: +1. [Find and replace](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-project.html#replace_search_string_in_project) all instances of: +```java +//* Ad ID implementation +``` +with: +```java +/* Ad ID implementation +``` +2. Sync Project with Gradle files using: File -> Sync Project with Gradle Files \ No newline at end of file diff --git a/Documentation/api-reference.md b/Documentation/api-reference.md new file mode 100644 index 00000000..218a5dc7 --- /dev/null +++ b/Documentation/api-reference.md @@ -0,0 +1,482 @@ +# Adobe Experience Platform Identity for Edge Network Extension - Android + +## Prerequisites + +Refer to the [Getting Started Guide](getting-started.md) + +## API reference + +| APIs | +| ----------------------------------------------------- | +| [extensionVersion](#extensionVersion) | +| [getExperienceCloudId](#getExperienceCloudId) | +| [getIdentities](#getIdentities) | +| [getUrlVariables](#getUrlVariables) | +| [registerExtension](#registerExtension) | +| [removeIdentity](#removeIdentity) | +| [resetIdentities](#resetIdentities) | +| [setAdvertisingIdentifier](#setAdvertisingIdentifier) | +| [updateIdentities](#updateIdentities) | + +------ + +### extensionVersion + +The extensionVersion() API returns the version of the Identity for Edge Network extension. + +#### Java + +##### Syntax +```java +public static String extensionVersion() +``` + +##### Example +```java +String extensionVersion = Identity.extensionVersion(); +``` +------ + +### getExperienceCloudId + +This API retrieves the Experience Cloud ID (ECID) that was generated when the app was initially launched. This ID is preserved between app upgrades, is saved and restored during the standard application backup process, and is removed at uninstall. + +> **Note** +> The ECID value is returned via the `AdobeCallback`. When `AdobeCallbackWithError` is provided to this API, the timeout value is 500ms. If the operation times out or an unexpected error occurs, the `fail` method is called with the appropriate `AdobeError`. + +#### Java + +##### Syntax +```java +public static void getExperienceCloudId(final AdobeCallback callback); +``` + +* _callback_ is invoked after the ECID is available. The callback may be invoked on a different thread. + +##### Example +```java +Identity.getExperienceCloudId(new AdobeCallback() { + @Override + public void call(String id) { + //Handle the ID returned here + } +}); +``` + +------ + +### getIdentities + +Get all the identities in the Identity for Edge Network extension, including customer identifiers which were previously added. + +> **Note** +> When `AdobeCallbackWithError` is provided, and you are fetching the identities from the Mobile SDK, the timeout value is 500ms. If the operation times out or an unexpected error occurs, the `fail` method is called with the appropriate `AdobeError`. + +#### Java + +##### Syntax +```java +public static void getIdentities(final AdobeCallback callback); +``` +* _callback_ is invoked after the identities are available. The return format is an instance of [IdentityMap](api-reference.md#identitymap). The callback may be invoked on a different thread. + +##### Example +```java +Identity.getIdentities(new AdobeCallback() { + @Override + public void call(IdentityMap identityMap) { + //Handle the IdentityMap returned here + } +}); +``` + +------ + +### getUrlVariables +> **Note** +> This API is available with version 1.1.0 and above. + +This API returns the identifiers in URL query parameter format for consumption in **hybrid mobile applications**. There is no leading & or ? punctuation as the caller is responsible for placing the variables in their resulting URL in the correct locations. If an error occurs while retrieving the URL variables, the callback handler will be called with a null value. Otherwise, the encoded string is returned, for example: `"adobe_mc=TS%3DTIMESTAMP_VALUE%7CMCMID%3DYOUR_ECID%7CMCORGID%3D9YOUR_EXPERIENCE_CLOUD_ID"` + +* The `adobe_mc` attribute is an URL encoded list that contains: + * `MCMID` - Experience Cloud ID \(ECID\) + * `MCORGID` - Experience Cloud Org ID + * `TS` - A timestamp taken when this request was made + +> **Note** +> When `AdobeCallbackWithError` is provided, and you are fetching the url variables from the Mobile SDK, the timeout value is 500ms. If the operation times out or an unexpected error occurs, the `fail` method is called with the appropriate `AdobeError`. + +#### Java + +##### Syntax +```java +public static void getUrlVariables(final AdobeCallback callback); +``` +* _callback_ has an NSString value that contains the visitor identifiers as a query string after the service request is complete. + +##### Example +```java +Identity.getUrlVariables(new AdobeCallback() { + @Override + public void call(String urlVariablesString) { + //handle the URL query parameter string here + //For example, open the URL in a webView + WebView webView; + webView = (WebView)findViewById(R.id.your_webview); // initialize with your webView + webview.loadUrl("https://example.com?" + urlVariablesString); + } +}); +``` + +------ + +### registerExtension + +Registers the Identity for Edge Network extension with the Mobile Core extension. + +> **Note** +> If your use-case covers both Edge Network and Adobe Experience Cloud Solutions extensions, you need to register Identity for Edge Network and Identity for Experience Cloud Identity Service from Mobile Core extensions. For more details, see the [frequently asked questions](https://aep-sdks.gitbook.io/docs/foundation-extensions/identity-for-edge-network/identity-faq#q-i-am-using-aep-edge-and-adobe-solutions-extensions-which-identity-extension-should-i-install-and-register). + +#### Java + +##### Syntax +```java +public static void registerExtension() +``` + +##### Example +```java +import com.adobe.marketing.mobile.edge.identity.Identity + +... +Identity.registerExtension(); +``` + +------ + +### removeIdentity + +Remove the identity from the stored client-side [IdentityMap](#identitymap). The Identity extension will stop sending the identifier to the Edge Network. Using this API does not remove the identifier from the server-side User Profile Graph or Identity Graph. + +Identities with an empty _id_ or _namespace_ are not allowed and are ignored. + +Removing identities using a reserved namespace is not allowed using this API. The reserved namespaces are: + +* ECID +* IDFA +* GAID + +#### Java + +##### Syntax +```java +public static void removeIdentity(final IdentityItem item, final String namespace); +``` + +##### Example +```java +IdentityItem item = new IdentityItem("user@example.com"); +Identity.removeIdentity(item, "Email"); +``` + +------ + +### resetIdentities + +Clears all identities stored in the Identity extension and generates a new Experience Cloud ID (ECID). Using this API does not remove the identifiers from the server-side User Profile Graph or Identity Graph. + +This is a destructive action, since once an ECID is removed it cannot be reused. The new ECID generated by this API can increase metrics like unique visitors when a new user profile is created. + +Some example use cases for this API are: +* During debugging, to see how new ECIDs (and other identifiers paired with it) behave with existing rules and metrics. +* A last-resort reset for when an ECID should no longer be used. + +This API is not recommended for: +* Resetting a user's consent and privacy settings; see [Privacy and GDPR](https://aep-sdks.gitbook.io/docs/resources/privacy-and-gdpr). +* Removing existing custom identifiers; use the [`removeIdentity`](#removeidentity) API instead. +* Removing a previously synced advertising identifier after the advertising tracking settings were changed by the user; use the [`setAdvertisingIdentifier`](https://aep-sdks.gitbook.io/docs/foundation-extensions/mobile-core/identity/identity-api-reference#setadvertisingidentifier) API instead. + +> **Warning** +>The Identity for Edge Network extension does not read the Mobile SDK's privacy status, and therefore setting the SDK's privacy status to opt-out will not automatically clear the identities from the Identity for Edge Network extension. See [`MobileCore.resetIdentities`](https://aep-sdks.gitbook.io/docs/foundation-extensions/mobile-core/mobile-core-api-reference#resetidentities) for more details. + +------ + +### setAdvertisingIdentifier + +When this API is called with a valid advertising identifier, the Identity for Edge Network extension includes the advertising identifier in the XDM Identity Map using the _GAID_ (Google Advertising ID) namespace. If the API is called with the empty string (`""`), `null`, or the all-zeros UUID string values, the GAID is removed from the XDM Identity Map (if previously set). + +The GAID is preserved between app upgrades, is saved and restored during the standard application backup process, and is removed at uninstall. + +> **Warning** +> In order to enable collection of the user's current advertising tracking authorization selection for the provided advertising identifier, you need to install and register the [AEPEdgeConsent](https://aep-sdks.gitbook.io/docs/foundation-extensions/consent-for-edge-network) extension and update the [AEPEdge](https://aep-sdks.gitbook.io/docs/foundation-extensions/experience-platform-extension) dependency to minimum 1.3.2. + +> **Note** +> These examples require Google Play Services to be configured in your mobile application, and use the Google Mobile Ads Lite SDK. For instructions on how to import the SDK and configure your `ApplicationManifest.xml` file, see [Google Mobile Ads Lite SDK setup](https://developers.google.com/admob/android/lite-sdk). + +> **Note** +> These are just implementation examples. For more information about advertising identifiers and how to handle them correctly in your mobile application, see [Google Play Services documentation about AdvertisingIdClient](https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient). + +```java +public static void setAdvertisingIdentifier(final String advertisingIdentifier); +``` +- _advertisingIdentifier_ is an ID string that provides developers with a simple, standard system to continue to track ads throughout their apps. + +##### Example +
+ import ... + +```java +import com.google.android.gms.ads.identifier.AdvertisingIdClient; +import com.google.android.gms.common.GooglePlayServicesNotAvailableException; +import com.google.android.gms.common.GooglePlayServicesRepairableException; +import java.io.IOException; +import android.util.Log; +``` +
+ +```java +... +@Override +public void onResume() { + super.onResume(); + ... + new Thread(new Runnable() { + @Override + public void run() { + String advertisingIdentifier = null; + + try { + AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext()); + if (adInfo != null) { + if (!adInfo.isLimitAdTrackingEnabled()) { + advertisingIdentifier = adInfo.getId(); + } else { + Log.d("ExampleActivity", "Limit Ad Tracking is enabled by the user, cannot process the advertising identifier"); + } + } + + } catch (IOException e) { + // Unrecoverable error connecting to Google Play services (e.g., + // the old version of the service doesn't support getting AdvertisingId). + Log.e("ExampleActivity", "IOException while retrieving the advertising identifier " + e.getLocalizedMessage()); + } catch (GooglePlayServicesNotAvailableException e) { + // Google Play services is not available entirely. + Log.e("ExampleActivity", "GooglePlayServicesNotAvailableException while retrieving the advertising identifier " + e.getLocalizedMessage()); + } catch (GooglePlayServicesRepairableException e) { + // Google Play services is not installed, up-to-date, or enabled. + Log.e("ExampleActivity", "GooglePlayServicesRepairableException while retrieving the advertising identifier " + e.getLocalizedMessage()); + } + + MobileCore.setAdvertisingIdentifier(advertisingIdentifier); + } + }).start(); +} +``` + +#### Kotlin + +##### Syntax +```kotlin +public fun setAdvertisingIdentifier(advertisingIdentifier: String) +``` +- _advertisingIdentifier_ is an ID string that provides developers with a simple, standard system to continue to track ads throughout their apps. + +##### Example +
+ import ... + +```kotlin +import android.content.Context +import com.google.android.gms.ads.identifier.AdvertisingIdClient +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.common.GooglePlayServicesRepairableException +import java.io.IOException +import android.util.Log +``` +
+ +```kotlin +suspend fun getGAID(applicationContext: Context): String { + var adID = "" + try { + val idInfo = AdvertisingIdClient.getAdvertisingIdInfo(applicationContext) + if (idInfo.isLimitAdTrackingEnabled) { + Log.d("ExampleActivity", "Limit Ad Tracking is enabled by the user, setting ad ID to \"\"") + return adID + } + Log.d("ExampleActivity", "Limit Ad Tracking disabled; ad ID value: ${idInfo.id}") + adID = idInfo.id + } catch (e: GooglePlayServicesNotAvailableException) { + Log.e("ExampleActivity", "GooglePlayServicesNotAvailableException while retrieving the advertising identifier ${e.localizedMessage}") + } catch (e: GooglePlayServicesRepairableException) { + Log.e("ExampleActivity", "GooglePlayServicesRepairableException while retrieving the advertising identifier ${e.localizedMessage}") + } catch (e: IOException) { + Log.e("ExampleActivity", "IOException while retrieving the advertising identifier ${e.localizedMessage}") + } + Log.d("ExampleActivity", "Returning ad ID value: $adID") + return adID +} +``` +Call site: +
+ import ... + +```kotlin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +``` +
+ +```kotlin + // Create background coroutine scope to fetch ad ID value +val scope = CoroutineScope(Dispatchers.IO).launch { + val adID = sharedViewModel.getGAID(context.applicationContext) + Log.d("ExampleActivity", "Sending ad ID value: $adID to MobileCore.setAdvertisingIdentifier") + + MobileCore.setAdvertisingIdentifier(adID) +} +``` + +------ + +### updateIdentities + +Update the currently known identities within the SDK. The Identity extension will merge the received identifiers with the previously saved ones in an additive manner, no identities are removed from this API. + +Identities with an empty _id_ or _namespace_ are not allowed and are ignored. + +Updating identities using a reserved namespace is not allowed using this API. The reserved namespaces are: + +* ECID +* IDFA +* GAID + +#### Java + +##### Syntax +```java +public static void updateIdentities(final IdentityMap identityMap); +``` + +##### Example +```java +IdentityItem item = new IdentityItem("user@example.com"); +IdentityMap identityMap = new IdentityMap(); +identityMap.addItem(item, "Email") +Identity.updateIdentities(identityMap); +``` + +------ + +## Public Classes + +### IdentityMap + +Defines a map containing a set of end user identities, keyed on either namespace integration code or the namespace ID of the identity. The values of the map are an array, meaning that more than one identity of each namespace may be carried. + +The format of the IdentityMap class is defined by the [XDM Identity Map Schema](https://github.com/adobe/xdm/blob/master/docs/reference/mixins/shared/identitymap.schema.md). + +For more information, please read an overview of the [AEP Identity Service](https://experienceleague.adobe.com/docs/experience-platform/identity/home.html). + +```text +"identityMap" : { + "Email" : [ + { + "id" : "user@example.com", + "authenticatedState" : "authenticated", + "primary" : false + } + ], + "Phone" : [ + { + "id" : "1234567890", + "authenticatedState" : "ambiguous", + "primary" : false + }, + { + "id" : "5557891234", + "authenticatedState" : "ambiguous", + "primary" : false + } + ], + "ECID" : [ + { + "id" : "44809014977647551167356491107014304096", + "authenticatedState" : "ambiguous", + "primary" : true + } + ] + } +``` + +**Example** + +```java +// Construct +IdentityMap identityMap = new IdentityMap(); + +// Add an item +IdentityItem item = new IdentityItem("user@example.com"); +identityMap.addItem(item, "Email"); + +// Remove an item +IdentityItem item = new IdentityItem("user@example.com"); +identityMap.removeItem(item, "Email"); + +// Get a list of items for a given namespace +List items = identityMap.getIdentityItemsForNamespace("Email"); + +// Get a list of all namespaces used in current IdentityMap +List namespaces = identityMap.getNamespaces(); + +// Check if IdentityMap has no identities +boolean hasNotIdentities = identityMap.isEmpty(); +``` + +------ + +### IdentityItem + +Defines an identity to be included in an [IdentityMap](#identitymap). + +The format of the IdentityItem class is defined by the [XDM Identity Item Schema](https://github.com/adobe/xdm/blob/master/docs/reference/datatypes/identityitem.schema.md). + +**Example** + +```java +// Construct +IdentityItem item = new IdentityItem("identifier"); + +IdentityItem item = new IdentityItem("identifier", AuthenticatedState.AUTHENTICATED, false); + + +// Getters +String id = item.getId(); + +AuthenticatedState state = item.getAuthenticatedState(); + +boolean primary = item.isPrimary(); +``` + +------ + +### AuthenticatedState + +Defines the state an [Identity Item](api-reference.md#identityitem) is authenticated for. + +The possible authenticated states are: + +* Ambiguous - the state is ambiguous or not defined +* Authenticated - the user is identified by a login or similar action +* LoggedOut - the user was identified by a login action at a previous time, but is not logged in now + +**Syntax** + +```java +public enum AuthenticatedState { + AMBIGUOUS("ambiguous"), + AUTHENTICATED("authenticated"), + LOGGED_OUT("loggedOut"); +} +``` diff --git a/Documentation/assets/find-and-replace-all-example.png b/Documentation/assets/find-and-replace-all-example.png new file mode 100644 index 00000000..fb7d7942 Binary files /dev/null and b/Documentation/assets/find-and-replace-all-example.png differ diff --git a/Documentation/assets/find-and-replace-shortcuts.png b/Documentation/assets/find-and-replace-shortcuts.png new file mode 100644 index 00000000..9bff8022 Binary files /dev/null and b/Documentation/assets/find-and-replace-shortcuts.png differ diff --git a/Documentation/assets/new_adid_setting_optin.png b/Documentation/assets/new_adid_setting_optin.png new file mode 100644 index 00000000..9dc76449 Binary files /dev/null and b/Documentation/assets/new_adid_setting_optin.png differ diff --git a/Documentation/assets/new_adid_setting_optout.png b/Documentation/assets/new_adid_setting_optout.png new file mode 100644 index 00000000..153696fc Binary files /dev/null and b/Documentation/assets/new_adid_setting_optout.png differ diff --git a/Documentation/assets/old_adid_setting_optin.png b/Documentation/assets/old_adid_setting_optin.png new file mode 100644 index 00000000..1db468ef Binary files /dev/null and b/Documentation/assets/old_adid_setting_optin.png differ diff --git a/Documentation/assets/old_adid_setting_optout.png b/Documentation/assets/old_adid_setting_optout.png new file mode 100644 index 00000000..987814b8 Binary files /dev/null and b/Documentation/assets/old_adid_setting_optout.png differ diff --git a/Documentation/assets/old_adid_setting_optout_prompt.png b/Documentation/assets/old_adid_setting_optout_prompt.png new file mode 100644 index 00000000..0b2d51a9 Binary files /dev/null and b/Documentation/assets/old_adid_setting_optout_prompt.png differ diff --git a/Documentation/assets/sync-project-gradle-example.png b/Documentation/assets/sync-project-gradle-example.png new file mode 100644 index 00000000..6557048d Binary files /dev/null and b/Documentation/assets/sync-project-gradle-example.png differ diff --git a/Documentation/getting-started-test-app.md b/Documentation/getting-started-test-app.md new file mode 100644 index 00000000..3dde45fb --- /dev/null +++ b/Documentation/getting-started-test-app.md @@ -0,0 +1,104 @@ +# Getting started with the test app + +## Testing tips for Android advertising identifier +See Google's [Advertising ID help article](https://support.google.com/googleplay/android-developer/answer/6048248?hl=en) for the latest requirements to access ad ID through `AdvertisingIdClient` APIs. + +Developers using ad ID should get the value from the API each time it is used, as permissions for ad tracking and/or the value of the ID itself may be changed at any time. + +To detect and handle changes in ad ID value or tracking authorization state, it may be helpful to use: +- A getter helper method that all requests for ad ID value are routed through. +- A check in the app lifecycle foreground event (or equivalent). + +In some Android environments, the ad ID tracking authorization is controlled using a toggle where the existing ad ID value remains unchanged: + +[Old ad ID settings page - opt-in state](./assets/old_adid_setting_optin.png) +[Old ad ID settings page - opt-out prompt](./assets/old_adid_setting_optout_prompt.png) +[Old ad ID settings page - opt-out state](./assets/old_adid_setting_optout.png) + +Based on Android emulator (Pixel_3a_API_32_arm64-v8a) testing for the ad ID toggle view using the Google Mobile Ads Lite SDK: +- Ad ID settings can be accessed through the device settings: Settings -> Privacy -> Ads. On this page: + - Users are opted-in to ad ID tracking by default. + - The ad ID value can be reset; this option replaces the old value with a new one. + - Ad tracking authorization status can be changed using the toggle. + - The ad ID value can be viewed. +- There is no ad tracking permission prompt shown to the user on first app launch. +- Changes in ad tracking authorization status or ad ID value do not terminate the app. +- When ad tracking is limited, the Mobile Ads Lite SDK still returns the device's current valid ad ID, +therefore using the [`isLimitAdTrackingEnabled()`](https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.Info#isLimitAdTrackingEnabled()) API to determine tracking authorization status before accessing ad ID value is recommended. See Google's API reference on [`getId()`](https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.Info#public-string-getid) for the latest guidance on accessing the ad ID value and expected return values. + +In other Android environments, the ad ID tracking authorization is controlled using a delete option that replaces the existing ad ID value with an all-zeros value until it is recreated by user's selection: + +[New ad ID settings page - opt-in state](./assets/new_adid_setting_optin.png) +[New ad ID settings page - opt-out state](./assets/new_adid_setting_optout.png) + +## Android Ads SDKs +## Google Mobile Ads Lite SDK +The [Google Mobile Ads Lite SDK](https://developers.google.com/admob/android/lite-sdk) is a way to use ads APIs without including the full size [Google Mobile Ads SDK](https://developers.google.com/admob/android/quick-start). +See API reference for [`AdvertisingIdClient`](https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient) and [`AdvertisingIdClient.Info`](https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.Info); the latter provides the APIs for getting the ad ID value and tracking authorization status. + +The Google AdMob SDK requires an application ID specified in the `AndroidManifest.xml` when the SDK is included in the build, otherwise the app will crash. However, for just ad ID testing purposes, the SDK doesn't have to be initialized. See Google's [quick start guide](https://developers.google.com/admob/android/quick-start#import_the_mobile_ads_sdk) for a detailed implementation guide (and a free sample app ID provided by Google for testing purposes in step 3). + +#### Implementation example +Using a getter to return the ad ID value. Key points to note: +- Use of a background coroutine scope from the call site. +- Checking the ad tracking authorization status to return the appropriate ad ID value. +```kotlin +import android.content.Context +import android.util.Log +import com.google.android.gms.ads.identifier.AdvertisingIdClient +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.common.GooglePlayServicesRepairableException +import java.io.IOException + +/** +* Async method that retrieves the ad ID from the `AdvertisingIdClient.Info` (from Google's Mobile Ads Lite SDK). +* Sanitizes ad ID tracking disabled state and any exceptions to the empty string (`""`), for easy use with `MobileCore` ad ID APIs. +* Should *only* be called from a background thread/coroutine. +* +* @param applicationContext: The application context that has the advertising ID provider to obtain the ad ID from. +* @return ad ID string: the ad ID value from the provider if available and tracking is allowed, empty string otherwise. +*/ +suspend fun getGAID(applicationContext: Context): String { + var adID = "" + try { + val idInfo = AdvertisingIdClient.getAdvertisingIdInfo(applicationContext) + if (idInfo.isLimitAdTrackingEnabled) { + Log.d(LOG_TAG, "Limit Ad Tracking is enabled by the user, setting ad ID to \"\"") + return adID + } + Log.d(LOG_TAG, "Limit Ad Tracking disabled; ad ID value: ${idInfo.id}") + adID = idInfo.id + } catch (e: GooglePlayServicesNotAvailableException) { + Log.d(LOG_TAG, "GooglePlayServicesNotAvailableException while retrieving the advertising identifier ${e.localizedMessage}") + } catch (e: GooglePlayServicesRepairableException) { + Log.d(LOG_TAG, "GooglePlayServicesRepairableException while retrieving the advertising identifier ${e.localizedMessage}") + } catch (e: IOException) { + Log.d(LOG_TAG, "IOException while retrieving the advertising identifier ${e.localizedMessage}") + } + Log.d(LOG_TAG, "Returning ad ID value: $adID") + return adID +} +``` +Call site: +```kotlin + // Create IO (background) coroutine scope to fetch ad ID value +val scope = CoroutineScope(Dispatchers.IO).launch { + val adID = sharedViewModel.getGAID(context.applicationContext) + Log.d(LOG_TAG, "Sending ad ID value: $adID to MobileCore.setAdvertisingIdentifier") + MobileCore.setAdvertisingIdentifier(adID) +} +``` + +Required normal permissions to use ad ID (Android 13 and above): +```xml + +``` +For more specifics on the use of this permission in the context of Android version requirements and permission merging through SDKs, see the [AdvertisingIdClient.Info documentation](https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.Info). + +## AndroidX Ads SDK +Overview: https://developer.android.com/jetpack/androidx/releases/ads +See the overview for official releases; the latest version is still in alpha and may not be fully supported. +`AdvertisingIdClient` API reference: https://developer.android.com/reference/androidx/ads/identifier/AdvertisingIdClient + +Based on testing with SDK version 1.0.0-alpha04 on emulator Pixel_3a_API_32_arm64-v8a, the SDK's [`AdvertisingIdClient.isAdvertisingIdProviderAvailable(Context)`](https://developer.android.com/reference/androidx/ads/identifier/AdvertisingIdClient#isAdvertisingIdProviderAvailable(android.content.Context)) does not return `true`, even when a valid app `Context` is provided. See additional source: https://stackoverflow.com/questions/59217195/how-do-i-use-or-implement-an-android-advertising-id-provider +Following the guide for ad ID may therefore not work: https://developer.android.com/training/articles/ad-id diff --git a/Documentation/getting-started.md b/Documentation/getting-started.md new file mode 100644 index 00000000..c3aace0f --- /dev/null +++ b/Documentation/getting-started.md @@ -0,0 +1,60 @@ +## Getting started + +The Adobe Experience Platform Identity for Edge Network extension has the following peer dependency, which must be installed prior to installing the identity extension: +- [Mobile Core](https://aep-sdks.gitbook.io/docs/foundation-extensions/mobile-core) + +## Add the AEP Identity extension to your app + +### Download and import the Identity extension + +> :information_source: The following instructions are for configuring an application using Adobe Experience Platform Edge mobile extensions. If an application will include both Edge Network and Adobe Solution extensions, both the Identity for Edge Network and Identity for Experience Cloud ID Service extensions are required. Find more details in the [Frequently Asked Questions](https://aep-sdks.gitbook.io/docs/foundation-extensions/identity-for-edge-network/identity-faq) page. + + +### Java + +1. Add the Mobile Core and Edge extensions to your project using the app's Gradle file. + + ```java + implementation 'com.adobe.marketing.mobile:core:1.+' + implementation 'com.adobe.marketing.mobile:edge:1.+' + implementation 'com.adobe.marketing.mobile:edgeidentity:1.+' + ``` + +2. Import the Mobile Core and Edge extensions in your Application class. + + ```java + import com.adobe.marketing.mobile.MobileCore; + import com.adobe.marketing.mobile.Edge; + import com.adobe.marketing.mobile.edge.identity.Identity; + ``` + +3. Register the Identity for Edge Extension with MobileCore: + +### Java + +```java +public class MobileApp extends Application { + + @Override + public void onCreate() { + super.onCreate(); + MobileCore.setApplication(this); + try { + Edge.registerExtension(); + Identity.registerExtension(); + // register other extensions + MobileCore.start(new AdobeCallback () { + @Override + public void call(Object o) { + MobileCore.configureWithAppID("yourAppId"); + } + }); + + } catch (Exception e) { + ... + } + + + } +} +``` diff --git a/Makefile b/Makefile index 0d7687aa..81fa030c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ EXTENSION-LIBRARY-FOLDER-NAME = edgeidentity +TEST-APP-FOLDER-NAME = app BUILD-ASSEMBLE-LOCATION = ./ci/assemble ROOT_DIR=$(shell git rev-parse --show-toplevel) @@ -10,14 +11,25 @@ LIB_VERSION = $(shell cat $(ROOT_DIR)/code/gradle.properties | grep "moduleVersi SOURCE_FILE_DIR = $(ROOT_DIR)/code/$(PROJECT_NAME) AAR_FILE_DIR = $(ROOT_DIR)/code/$(PROJECT_NAME)/build/outputs/aar -create-ci: clean - (mkdir -p ci) +init: + git config core.hooksPath .githooks + +format: + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) spotlessApply) + (./code/gradlew -p code/$(TEST-APP-FOLDER-NAME) spotlessApply) + +format-check: + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) spotlessCheck) + (./code/gradlew -p code/$(TEST-APP-FOLDER-NAME) spotlessCheck) clean: (rm -rf ci) (rm -rf $(AAR_FILE_DIR)) (./code/gradlew -p code clean) +create-ci: clean + (mkdir -p ci) + ci-build: create-ci (mkdir -p ci/assemble) @@ -27,7 +39,7 @@ ci-build: create-ci (cp -r ./code/$(EXTENSION-LIBRARY-FOLDER-NAME)/build $(BUILD-ASSEMBLE-LOCATION)) ci-build-app: - (./code/gradlew -p code/app assemble) + (./code/gradlew -p code/$(TEST-APP-FOLDER-NAME) assemble) ci-unit-test: create-ci (mkdir -p ci/unit-test) @@ -59,4 +71,3 @@ ci-publish-staging: clean build-release ci-publish-main: clean build-release (./code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} publishReleasePublicationToSonatypeRepository -Prelease) - diff --git a/README.md b/README.md index dc174ab7..2f5f7753 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,34 @@ The Adobe Experience Platform Edge Identity is a mobile extension for the [Adobe Integrate the Edge Identity extension into your app by including the following in your gradle file's `dependencies`: -``` +```gradle implementation 'com.adobe.marketing.mobile:edgeidentity:1.+' implementation 'com.adobe.marketing.mobile:edge:1.+' implementation 'com.adobe.marketing.mobile:core:1.+' +implementation 'com.adobe.marketing.mobile:edgeconsent:1.+' // Recommended when using the setAdvertisingIdentifier API ``` ### Development **Open the project** -To open and run the project, open the `code/settings.gradle` file in Android Studio +To open and run the project, open the `code/settings.gradle` file in Android Studio. + +**Data Collection mobile property prerequisites** + +The test app needs to be configured with the following edge extensions before it can be used: +- Mobile Core (installed by default) +- [Edge](https://aep-sdks.gitbook.io/docs/foundation-extensions/experience-platform-extension) +- [Edge Identity](https://aep-sdks.gitbook.io/docs/foundation-extensions/identity-for-edge-network) +- [Edge Consent](https://aep-sdks.gitbook.io/docs/foundation-extensions/consent-for-edge-network) (recommended when using the setAdvertisingIdentifier API) **Run demo application** -Once you opened the project in Android Studio (see above), select the `app` runnable and your favorite simulator and run the program. +1. In the test app, set your `ENVIRONMENT_FILE_ID` in `EdgeIdentityApplication.kt`. +2. Select the `app` runnable with the desired emulator and run the program. + +> **Note** +> To enable GAID related advertising identifier features, follow the [documentation](Documentation/README.md#advertising-identifier) for the required setup steps. **View the platform events with Assurance** @@ -36,9 +49,45 @@ $ adb shell am start -W -a android.intent.action.VIEW -d "testapp://main?adb_va Note: replace ADD_YOUR_SESSION_ID_HERE with your Assurance session identifier. -Once the connection is established and the events list starts getting populated, you can filter the events for this extension by typing `Edge Identity` in the `Search Events` search box. See full list of available events [here](https://aep-sdks.gitbook.io/docs/beta/experience-platform-extension/experience-platform-debugging#event-types-handled-by-the-aep-mobile-extension). +Once the connection is established and the events list starts getting populated, you can filter the events for this extension by typing `Edge Identity` in the `Search Events` search box. + +**Development on M1 Macs** +M1 Macs may run into errors during the build process, specifically finding the npm installation directory. + +``` +Execution failed for task ':spotlessInternalRegisterDependencies'. + +Can't automatically determine npm executable and none was specifically supplied! + +Spotless tries to find your npm executable automatically. It looks for npm in the following places: +- An executable referenced by the java system property 'npm.exec' - if such a system property exists. +- The environment variable 'NVM_BIN' - if such an environment variable exists. +- The environment variable 'NVM_SYMLINK' - if such an environment variable exists. +- The environment variable 'NODE_PATH' - if such an environment variable exists. +- In your 'PATH' environment variable + +If autodiscovery fails for your system, try to set one of the environment variables correctly or +try setting the system property 'npm.exec' in the build process to override autodiscovery. +``` + +To address this: +- Update Android Studio to the latest version (minimum version Bumblebee Patch 1 should address this issue) +- Update the Android Gradle Plugin to the latest version (7.x.x as of this writing) + +If that does not address the issue, try installing node using the installer and not through homebrew: https://nodejs.org/en/download/ + +Please make sure that these build configuration changes are kept local; any build process dependencies (ex: Gradle version, packages) that are updated in this process should **not** be included in any PRs that are not specifically for updating the project's build configuration. + +### Code Format +This project uses the code formatting tools [Spotless](https://github.com/diffplug/spotless/tree/main/plugin-gradle) with [Prettier](https://prettier.io/) and [ktlint](https://github.com/pinterest/ktlint). Formatting is applied when the project is built from Gradle and is checked when changes are submitted to the CI build system. +Prettier requires [Node version](https://nodejs.org/en/download/releases/) 10+ + +To enable the Git pre-commit hook to apply code formatting on each commit, run the following to update the project's git config `core.hooksPath`: +``` +make init +``` ## Related Projects @@ -46,6 +95,10 @@ Once the connection is established and the events list starts getting populated, | ------------------------------------------------------------ | ------------------------------------------------------------ | | [AEP SDK Sample App for Android](https://github.com/adobe/aepsdk-sample-app-android) | Contains Android sample app for the AEP SDK. | +## Documentation + +Additional documentation for usage and SDK architecture can be found under the [Documentation](Documentation) directory. + ## Contributing Contributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING.md) for more information. diff --git a/code/app/build.gradle b/code/app/build.gradle index e88e1780..c3abf6ab 100644 --- a/code/app/build.gradle +++ b/code/app/build.gradle @@ -1,28 +1,26 @@ -/* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: "com.diffplug.spotless" + +spotless { + kotlin { + target "src/*/java/**/*.kt" + ktlint('0.41.0') + licenseHeaderFile "../../config/formatter/adobe.header.txt" + } +} android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" + compileSdkVersion 31 defaultConfig { applicationId "com.adobe.marketing.edge.identity.app" minSdkVersion 19 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" + multiDexEnabled = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -59,6 +57,15 @@ dependencies { implementation project(':edgeidentity') implementation 'com.adobe.marketing.mobile:core:1+' implementation 'com.adobe.marketing.mobile:identity:1+' - implementation 'com.adobe.marketing.mobile:edge:1+' + implementation 'com.adobe.marketing.mobile:edgeconsent:1.+' + implementation('com.adobe.marketing.mobile:edge:1.3.2-SNAPSHOT') { + exclude group: 'com.adobe.marketing.mobile', module:'edgeidentity' + } implementation 'com.adobe.marketing.mobile:assurance:1+' + + /* Ad ID implementation (pt. 1/5) + implementation("com.google.android.gms:play-services-ads-lite:20.6.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0") + /* Ad ID implementation (pt. 1/5) */ + implementation("androidx.multidex:multidex:2.0.1") } diff --git a/code/app/src/main/AndroidManifest.xml b/code/app/src/main/AndroidManifest.xml index c2c09393..18386222 100644 --- a/code/app/src/main/AndroidManifest.xml +++ b/code/app/src/main/AndroidManifest.xml @@ -15,13 +15,18 @@ + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> + + \ No newline at end of file diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt index bfd92c45..74a87125 100644 --- a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt @@ -1,13 +1,13 @@ /* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ package com.adobe.marketing.edge.identity.app @@ -16,11 +16,12 @@ import com.adobe.marketing.mobile.Assurance import com.adobe.marketing.mobile.Edge import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.edge.consent.Consent import com.adobe.marketing.mobile.edge.identity.Identity class EdgeIdentityApplication : Application() { - // Add your Launch Environment ID to configure the SDK from your Launch property - private var LAUNCH_ENVIRONMENT_ID: String = "" + // TODO: Set up the preferred Environment File ID from your mobile property configured in Data Collection UI + private var ENVIRONMENT_FILE_ID: String = "" override fun onCreate() { super.onCreate() @@ -29,12 +30,13 @@ class EdgeIdentityApplication : Application() { MobileCore.setApplication(this) MobileCore.setLogLevel(LoggingMode.VERBOSE) + Consent.registerExtension() Identity.registerExtension() Edge.registerExtension() Assurance.registerExtension() MobileCore.start { - MobileCore.configureWithAppID(LAUNCH_ENVIRONMENT_ID) + MobileCore.configureWithAppID(ENVIRONMENT_FILE_ID) } } -} \ No newline at end of file +} diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt index 65addbca..9fa81b1c 100644 --- a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt @@ -1,27 +1,27 @@ /* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ package com.adobe.marketing.edge.identity.app import android.os.Bundle import android.view.Menu -import com.google.android.material.navigation.NavigationView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.drawerlayout.widget.DrawerLayout import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController -import androidx.drawerlayout.widget.DrawerLayout -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar +import com.google.android.material.navigation.NavigationView class MainActivity : AppCompatActivity() { @@ -38,8 +38,12 @@ class MainActivity : AppCompatActivity() { val navController = findNavController(R.id.nav_host_fragment) // Passing each menu ID as a set of Ids because each // menu should be considered as top level destinations. - appBarConfiguration = AppBarConfiguration(setOf( - R.id.nav_get_identity, R.id.nav_custom_identity, R.id.nav_multiple_identity, R.id.nav_assurance), drawerLayout) + appBarConfiguration = AppBarConfiguration( + setOf( + R.id.nav_get_identity, R.id.nav_custom_identity, R.id.nav_multiple_identity, R.id.nav_assurance + ), + drawerLayout + ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) } diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt index b3432bf8..770f0812 100644 --- a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt @@ -1,13 +1,13 @@ /* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ package com.adobe.marketing.edge.identity.app.model @@ -15,6 +15,15 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.adobe.marketing.mobile.edge.identity.AuthenticatedState +/* Ad ID implementation (pt. 2/5) +import android.content.Context +import android.util.Log +import com.google.android.gms.ads.identifier.AdvertisingIdClient +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.common.GooglePlayServicesRepairableException +import java.io.IOException +/* Ad ID implementation (pt. 2/5) */*/ +private const val LOG_TAG = "Shared_View_Model" class SharedViewModel : ViewModel() { companion object { @@ -32,6 +41,9 @@ class SharedViewModel : ViewModel() { private val _ecidLegacyText = MutableLiveData("") val ecidLegacyText: LiveData = _ecidLegacyText + private val _urlVariablesText = MutableLiveData("") + val urlVariablesText: LiveData = _urlVariablesText + private val _identitiesText = MutableLiveData("") val identitiesText: LiveData = _identitiesText @@ -43,12 +55,19 @@ class SharedViewModel : ViewModel() { _ecidLegacyText.value = value } + fun setUrlVariablesValue(value: String) { + _urlVariablesText.value = value + } + fun setIdentitiesValue(value: String) { _identitiesText.value = value } // Models for Update Identities View + private val _adId = MutableLiveData("") + val adId: LiveData = _adId + private val _identifier = MutableLiveData("") val identifier: LiveData = _identifier @@ -64,6 +83,13 @@ class SharedViewModel : ViewModel() { private val _authenticatedStateId = MutableLiveData(null) val authenticatedStateId: LiveData = _authenticatedStateId + fun setAdId(value: String) { + if (_adId.value == value) { + return + } + _adId.value = value + } + fun setIdentifier(value: String) { if (_identifier.value == value) { return @@ -99,6 +125,37 @@ class SharedViewModel : ViewModel() { _authenticatedStateId.value = value } + /* Ad ID implementation (pt. 3/5) + /** + * Async method that retrieves the ad ID from the `AdvertisingIdClient` (from Google's gms.ads SDK). + * Sanitizes ad ID disabled and exceptions to the empty string (`""`), for easy use with `MobileCore` ad ID APIs. + * Should *only* be called from a background thread/coroutine. + * + * @param applicationContext: The application context that has the advertising ID provider to obtain the ad ID from. + * @return ad ID string; ad ID value from the provider if available and tracking is allowed, empty string otherwise. + */ + suspend fun getGAID(applicationContext: Context): String { + var adID = "" + try { + val idInfo = AdvertisingIdClient.getAdvertisingIdInfo(applicationContext) + if (idInfo.isLimitAdTrackingEnabled) { + Log.d(LOG_TAG, "Limit Ad Tracking is enabled by the user, setting ad ID to \"\"") + return adID + } + Log.d(LOG_TAG, "Limit Ad Tracking disabled; ad ID value: ${idInfo.id}") + adID = idInfo.id + } catch (e: GooglePlayServicesNotAvailableException) { + Log.d(LOG_TAG, "GooglePlayServicesNotAvailableException while retrieving the advertising identifier ${e.localizedMessage}") + } catch (e: GooglePlayServicesRepairableException) { + Log.d(LOG_TAG, "GooglePlayServicesRepairableException while retrieving the advertising identifier ${e.localizedMessage}") + } catch (e: IOException) { + Log.d(LOG_TAG, "IOException while retrieving the advertising identifier ${e.localizedMessage}") + } + Log.d(LOG_TAG, "Returning ad ID value: $adID") + return adID + } + /* Ad ID implementation (pt. 3/5) */*/ + // Models for Multiple Identities View private val _isEdgeIdentityRegistered = MutableLiveData(true) @@ -125,7 +182,6 @@ class SharedViewModel : ViewModel() { } val directIdentityRegisteredText: LiveData = _directIdentityRegisteredText - fun toggleEdgeIdentityRegistration() { if (_isEdgeIdentityRegistered.value == true) { _isEdgeIdentityRegistered.value = false @@ -145,4 +201,4 @@ class SharedViewModel : ViewModel() { _directIdentityRegisteredText.value = IDENTITY_IS_REGISTERED_STRING } } -} \ No newline at end of file +} diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt index 0fa690db..ce2ca455 100644 --- a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt @@ -1,13 +1,13 @@ /* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ package com.adobe.marketing.edge.identity.app.ui @@ -24,18 +24,18 @@ import com.adobe.marketing.mobile.Assurance class AssuranceFragment : Fragment() { override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? { val root = inflater.inflate(R.layout.fragment_assurance, container, false) - root.findViewById