From d0a1526a94dd596e02306ae8c3638ca58e24a323 Mon Sep 17 00:00:00 2001 From: Antonio Zugaldia Date: Thu, 18 Apr 2019 16:29:20 -0400 Subject: [android] Introduce AccountsManager to support SKU tokens in API requests (#14404) --- platform/android/LICENSE.md | 6 ++ platform/android/MapboxGLAndroidSDK/build.gradle | 1 + .../java/com/mapbox/mapboxsdk/AccountsManager.java | 112 +++++++++++++++++++++ .../src/main/java/com/mapbox/mapboxsdk/Mapbox.java | 13 +++ .../mapboxsdk/constants/MapboxConstants.java | 5 + .../com/mapbox/mapboxsdk/http/HttpRequestUrl.java | 4 +- .../mapboxsdk/module/telemetry/TelemetryImpl.java | 8 +- .../com/mapbox/mapboxsdk/storage/FileSource.java | 8 +- .../com/mapbox/mapboxsdk/AccountsManagerTest.java | 24 +++++ platform/android/gradle/dependencies.gradle | 2 + 10 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/AccountsManager.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/AccountsManagerTest.java diff --git a/platform/android/LICENSE.md b/platform/android/LICENSE.md index 35ae99120f..b269437fe6 100644 --- a/platform/android/LICENSE.md +++ b/platform/android/LICENSE.md @@ -71,6 +71,12 @@ License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) =========================================================================== +Mapbox GL uses portions of the Mapbox Accounts SDK for Android. +URL: [https://github.com/mapbox/mapbox-accounts-android](https://github.com/mapbox/mapbox-accounts-android) +License: [Mapbox Terms of Service](https://www.mapbox.com/tos/) + +=========================================================================== + Mapbox GL uses portions of the Mapbox Android Core Library. URL: [https://github.com/mapbox/mapbox-events-android](https://github.com/mapbox/mapbox-events-android) License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index 108f6315d8..510dc79d43 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -7,6 +7,7 @@ dependencies { api dependenciesList.mapboxAndroidTelemetry api dependenciesList.mapboxJavaGeoJSON api dependenciesList.mapboxAndroidGestures + api dependenciesList.mapboxAndroidAccounts implementation dependenciesList.mapboxJavaTurf implementation dependenciesList.supportAnnotations implementation dependenciesList.supportFragmentV4 diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/AccountsManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/AccountsManager.java new file mode 100644 index 0000000000..4fd0200a37 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/AccountsManager.java @@ -0,0 +1,112 @@ +package com.mapbox.mapboxsdk; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.text.format.DateUtils; + +import com.mapbox.android.accounts.v1.MapboxAccounts; +import com.mapbox.mapboxsdk.constants.MapboxConstants; + +/** + * REMOVAL OR MODIFICATION OF THE FOLLOWING CODE VIOLATES THE MAPBOX TERMS + * OF SERVICE + * + * The following code is used to access Mapbox's Mapping APIs. + * + * Removal or modification of this code when used with Mapbox's Mapping APIs + * can result in termination of your agreement and/or your account with + * Mapbox. + * + * Using this code to access Mapbox Mapping APIs from outside the Mapbox Maps + * SDK also violates the Mapbox Terms of Service. On Android, Mapping APIs + * should be accessed using the methods documented at + * https://www.mapbox.com/android. + * + * You can access the Mapbox Terms of Service at https://www.mapbox.com/tos/ + */ +class AccountsManager { + private static final String PREFERENCE_USER_ID = "com.mapbox.mapboxsdk.accounts.userid"; + private static final String PREFERENCE_TIMESTAMP = "com.mapbox.mapboxsdk.accounts.timestamp"; + private static final String PREFERENCE_SKU_TOKEN = "com.mapbox.mapboxsdk.accounts.skutoken"; + + private long timestamp; + private String skuToken; + + AccountsManager() { + String userId = validateUserId(); + validateRotation(userId); + } + + private String validateUserId() { + SharedPreferences sharedPreferences = getSharedPreferences(); + String userId = sharedPreferences.getString(PREFERENCE_USER_ID, ""); + if (TextUtils.isEmpty(userId)) { + userId = generateUserId(); + SharedPreferences.Editor editor = getSharedPreferences().edit(); + editor.putString(PREFERENCE_USER_ID, userId); + editor.apply(); + } + + return userId; + } + + private void validateRotation(String userId) { + SharedPreferences sharedPreferences = getSharedPreferences(); + timestamp = sharedPreferences.getLong(PREFERENCE_TIMESTAMP, 0L); + skuToken = sharedPreferences.getString(PREFERENCE_SKU_TOKEN, ""); + if (timestamp == 0L || TextUtils.isEmpty(skuToken)) { + skuToken = generateSkuToken(userId); + timestamp = persistRotation(skuToken); + } + } + + String getSkuToken() { + if (isExpired()) { + SharedPreferences sharedPreferences = getSharedPreferences(); + String userId = sharedPreferences.getString(PREFERENCE_USER_ID, ""); + skuToken = generateSkuToken(userId); + timestamp = persistRotation(skuToken); + } + + return skuToken; + } + + private boolean isExpired() { + return isExpired(getNow(), timestamp); + } + + static boolean isExpired(long now, long then) { + return ((now - then) > DateUtils.HOUR_IN_MILLIS); + } + + private long persistRotation(String skuToken) { + long now = getNow(); + SharedPreferences.Editor editor = getSharedPreferences().edit(); + editor.putLong(PREFERENCE_TIMESTAMP, now); + editor.putString(PREFERENCE_SKU_TOKEN, skuToken); + editor.apply(); + return now; + } + + @NonNull + private SharedPreferences getSharedPreferences() { + return Mapbox.getApplicationContext() + .getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE); + } + + static long getNow() { + return System.currentTimeMillis(); + } + + @NonNull + private String generateUserId() { + return MapboxAccounts.obtainEndUserId(); + } + + @NonNull + private String generateSkuToken(String userId) { + return MapboxAccounts.obtainMapsSkuUserToken(userId); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java index f5304017a5..5a1c49a0a4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java @@ -34,6 +34,8 @@ public final class Mapbox { private String accessToken; @Nullable private TelemetryDefinition telemetry; + @Nullable + private AccountsManager accounts; /** * Get an instance of Mapbox. @@ -56,6 +58,7 @@ public final class Mapbox { INSTANCE = new Mapbox(appContext, accessToken); if (isAccessTokenValid(accessToken)) { initializeTelemetry(); + INSTANCE.accounts = new AccountsManager(); } ConnectivityReceiver.instance(appContext); } @@ -87,6 +90,16 @@ public final class Mapbox { FileSource.getInstance(getApplicationContext()).setAccessToken(accessToken); } + /** + * Returns a SKU token, refreshed if necessary. This method is meant for internal SDK + * usage only. + * + * @return the SKU token + */ + public static String getSkuToken() { + return INSTANCE.accounts.getSkuToken(); + } + /** * Application context * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java index 2b69646fcc..ffcf8d74ec 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java @@ -12,6 +12,11 @@ public class MapboxConstants { */ public static final Locale MAPBOX_LOCALE = Locale.US; + /** + * The name of the desired preferences file for Android's SharedPreferences. + */ + public static final String MAPBOX_SHARED_PREFERENCES = "MapboxSharedPreferences"; + /** * Key used to switch storage to external in AndroidManifest.xml */ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUrl.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUrl.java index 37e3869692..ec4d90cbc7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUrl.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HttpRequestUrl.java @@ -2,6 +2,8 @@ package com.mapbox.mapboxsdk.http; import android.support.annotation.NonNull; +import com.mapbox.mapboxsdk.Mapbox; + public class HttpRequestUrl { private HttpRequestUrl() { @@ -22,7 +24,7 @@ public class HttpRequestUrl { } else { resourceUrl = resourceUrl + "&"; } - resourceUrl = resourceUrl + "events=true"; + resourceUrl = resourceUrl + "events=true&sku=" + Mapbox.getSkuToken(); } return resourceUrl; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java index 46a0c47d50..944999715a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/module/telemetry/TelemetryImpl.java @@ -4,8 +4,8 @@ import android.content.Context; import android.os.Bundle; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import com.mapbox.android.accounts.v1.MapboxAccounts; import com.mapbox.android.telemetry.AppUserTurnstile; import com.mapbox.android.telemetry.MapboxTelemetry; import com.mapbox.android.telemetry.SessionInterval; @@ -21,9 +21,8 @@ import java.util.UUID; public class TelemetryImpl implements TelemetryDefinition { - @Nullable - private MapboxTelemetry telemetry; - private Context appContext; + private final MapboxTelemetry telemetry; + private final Context appContext; public TelemetryImpl() { appContext = Mapbox.getApplicationContext(); @@ -42,6 +41,7 @@ public class TelemetryImpl implements TelemetryDefinition { public void onAppUserTurnstileEvent() { AppUserTurnstile turnstileEvent = new AppUserTurnstile(BuildConfig.MAPBOX_SDK_IDENTIFIER, BuildConfig.MAPBOX_SDK_VERSION); + turnstileEvent.setSkuId(MapboxAccounts.SKU_ID_MAPS_MAUS); telemetry.push(turnstileEvent); telemetry.push(MapEventFactory.buildMapLoadEvent(new PhoneState(appContext))); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index db99f255ac..cc4988b549 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -31,7 +31,6 @@ import java.util.concurrent.locks.ReentrantLock; public class FileSource { private static final String TAG = "Mbgl-FileSource"; - private static final String MAPBOX_SHARED_PREFERENCES = "MapboxSharedPreferences"; private static final String MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH = "fileSourceResourcesCachePath"; private static final Lock resourcesCachePathLoaderLock = new ReentrantLock(); private static final Lock internalCachePathLoaderLock = new ReentrantLock(); @@ -107,7 +106,8 @@ public class FileSource { */ @NonNull private static String getCachePath(@NonNull Context context) { - SharedPreferences preferences = context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE); + SharedPreferences preferences = context.getSharedPreferences( + MapboxConstants.MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE); String cachePath = preferences.getString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, null); if (!isPathWritable(cachePath)) { @@ -116,7 +116,7 @@ public class FileSource { // Reset stored cache path SharedPreferences.Editor editor = - context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit(); + context.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit(); editor.remove(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH).apply(); } @@ -306,7 +306,7 @@ public class FileSource { callback.onError(fileSourceActivatedMessage); } else { final SharedPreferences.Editor editor = - context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit(); + context.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit(); editor.putString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, path); editor.apply(); setResourcesCachePath(context, path); diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/AccountsManagerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/AccountsManagerTest.java new file mode 100644 index 0000000000..3f846e6640 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/AccountsManagerTest.java @@ -0,0 +1,24 @@ +package com.mapbox.mapboxsdk; + +import android.text.format.DateUtils; + +import org.junit.Assert; +import org.junit.Test; + +public class AccountsManagerTest { + @Test + public void testIsExpired() { + long now = AccountsManager.getNow(); + + long defaultValue = 0L; + long tooOld = now - DateUtils.HOUR_IN_MILLIS - 1; + long futureValue = now + 1; + long immediatePast = now - 1; + + Assert.assertTrue(AccountsManager.isExpired(now, defaultValue)); + Assert.assertTrue(AccountsManager.isExpired(now, tooOld)); + + Assert.assertFalse(AccountsManager.isExpired(now, futureValue)); + Assert.assertFalse(AccountsManager.isExpired(now, immediatePast)); + } +} diff --git a/platform/android/gradle/dependencies.gradle b/platform/android/gradle/dependencies.gradle index f9c6f2ac1b..4d95456284 100644 --- a/platform/android/gradle/dependencies.gradle +++ b/platform/android/gradle/dependencies.gradle @@ -11,6 +11,7 @@ ext { mapboxTelemetry : '4.4.1', mapboxCore : '1.3.0', mapboxGestures : '0.4.1', + mapboxAccounts : '0.1.0', supportLib : '27.1.1', constraintLayout: '1.1.2', uiAutomator : '2.1.3', @@ -39,6 +40,7 @@ ext { mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-sdk-geojson:${versions.mapboxServices}", mapboxAndroidTelemetry : "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxTelemetry}", mapboxAndroidGestures : "com.mapbox.mapboxsdk:mapbox-android-gestures:${versions.mapboxGestures}", + mapboxAndroidAccounts : "com.mapbox.mapboxsdk:mapbox-android-accounts:${versions.mapboxAccounts}", mapboxJavaTurf : "com.mapbox.mapboxsdk:mapbox-sdk-turf:${versions.mapboxServices}", junit : "junit:junit:${versions.junit}", -- cgit v1.2.1