Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[android] Introduce AccountsManager to support SKU tokens in API requests #14404

Merged
merged 16 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions platform/android/LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions platform/android/MapboxGLAndroidSDK/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public final class Mapbox {
private String accessToken;
@Nullable
private TelemetryDefinition telemetry;
@Nullable
private AccountsManager accounts;

/**
* Get an instance of Mapbox.
Expand All @@ -56,6 +58,7 @@ public static synchronized Mapbox getInstance(@NonNull Context context, @Nullabl
INSTANCE = new Mapbox(appContext, accessToken);
if (isAccessTokenValid(accessToken)) {
initializeTelemetry();
INSTANCE.accounts = new AccountsManager();
}
ConnectivityReceiver.instance(appContext);
}
Expand Down Expand Up @@ -87,6 +90,16 @@ public static void setAccessToken(String accessToken) {
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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit javadoc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

return INSTANCE.accounts.getSkuToken();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a developer doesn't provide an access token, eg. when using their own tile endpoint, this will throw a null pointer exception when the SDK tries to make a request.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is only called for Mapbox API URLs so it's safe to remove the validation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that part, :shipit:

}

/**
* Application context
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.support.annotation.NonNull;

import com.mapbox.mapboxsdk.Mapbox;

public class HttpRequestUrl {

private HttpRequestUrl() {
Expand All @@ -22,7 +24,7 @@ public static String buildResourceUrl(@NonNull String host, String resourceUrl,
} else {
resourceUrl = resourceUrl + "&";
}
resourceUrl = resourceUrl + "events=true";
resourceUrl = resourceUrl + "events=true&sku=" + Mapbox.getSkuToken();
}
return resourceUrl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
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;
Expand All @@ -21,9 +21,8 @@

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();
Expand All @@ -42,6 +41,7 @@ public TelemetryImpl() {
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)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
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();
Expand Down Expand Up @@ -107,7 +106,8 @@ public static synchronized FileSource getInstance(@NonNull Context context) {
*/
@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)) {
Expand All @@ -116,7 +116,7 @@ private static String getCachePath(@NonNull Context context) {

// 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();
}

Expand Down Expand Up @@ -306,7 +306,7 @@ public void onWritePermissionGranted() {
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
2 changes: 2 additions & 0 deletions platform/android/gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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}",
Expand Down