Removing telemetry and mapbox mobile events

This commit is contained in:
Petr Pokorny
2021-01-24 17:18:19 +01:00
parent 38763fe65b
commit e14070b4b0
102 changed files with 2598 additions and 1681 deletions

View File

@@ -13,8 +13,6 @@ import com.mapbox.android.accounts.v1.MapboxAccounts;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.log.Logger;
import static com.mapbox.mapboxsdk.constants.MapboxConstants.KEY_PREFERENCE_SKU_TOKEN;
/**
* REMOVAL OR MODIFICATION OF THE FOLLOWING CODE VIOLATES THE MAPBOX TERMS
* OF SERVICE
@@ -39,45 +37,19 @@ class AccountsManager {
private SharedPreferences sharedPreferences;
private String userId;
private String skuToken;
private long timestamp;
private boolean isManaged;
AccountsManager() {
isManaged = isSkuTokenManaged();
initialize();
}
@VisibleForTesting
AccountsManager(SharedPreferences sharedPreferences, boolean isManaged) {
AccountsManager(SharedPreferences sharedPreferences) {
this.sharedPreferences = sharedPreferences;
this.isManaged = isManaged;
initialize();
}
private void initialize() {
retrieveSkuTokenAndTimestamp();
if (isManaged) {
validateRotation();
}
}
private boolean isSkuTokenManaged() {
boolean value = MapboxConstants.DEFAULT_MANAGE_SKU_TOKEN;
try {
// Try getting a custom value from the app Manifest
ApplicationInfo appInfo = retrieveApplicationInfo();
if (appInfo.metaData != null) {
value = appInfo.metaData.getBoolean(
MapboxConstants.KEY_META_DATA_MANAGE_SKU_TOKEN,
MapboxConstants.DEFAULT_MANAGE_SKU_TOKEN
);
}
} catch (Exception exception) {
Logger.e(TAG, "Failed to read the package metadata: ", exception);
}
return value;
}
private ApplicationInfo retrieveApplicationInfo() throws PackageManager.NameNotFoundException {
@@ -86,33 +58,6 @@ class AccountsManager {
PackageManager.GET_META_DATA);
}
private void retrieveSkuTokenAndTimestamp() {
SharedPreferences sharedPreferences = getSharedPreferences();
skuToken = sharedPreferences.getString(KEY_PREFERENCE_SKU_TOKEN, "");
timestamp = sharedPreferences.getLong(PREFERENCE_TIMESTAMP, 0L);
}
private void validateRotation() {
if (TextUtils.isEmpty(skuToken) || timestamp == 0L) {
skuToken = generateSkuToken(getUserId());
timestamp = persistRotation(skuToken);
}
}
String getSkuToken() {
if (isManaged) {
if (isExpired()) {
skuToken = generateSkuToken(getUserId());
timestamp = persistRotation(skuToken);
}
} else {
SharedPreferences sharedPreferences = getSharedPreferences();
String notManagedSkuToken = sharedPreferences.getString(KEY_PREFERENCE_SKU_TOKEN, "");
skuToken = notManagedSkuToken;
}
return skuToken;
}
private boolean isExpired() {
return isExpired(getNow(), timestamp);
}
@@ -121,15 +66,6 @@ class AccountsManager {
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(KEY_PREFERENCE_SKU_TOKEN, skuToken);
editor.apply();
return now;
}
@NonNull
private SharedPreferences getSharedPreferences() {
if (sharedPreferences == null) {
@@ -167,9 +103,4 @@ class AccountsManager {
private String generateUserId() {
return MapboxAccounts.obtainEndUserId();
}
@NonNull
private String generateSkuToken(String userId) {
return MapboxAccounts.obtainMapsSkuUserToken(userId);
}
}

View File

@@ -11,7 +11,6 @@ import androidx.annotation.UiThread;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
import com.mapbox.mapboxsdk.utils.ThreadUtils;
@@ -37,8 +36,6 @@ public final class Mapbox {
@Nullable
private String accessToken;
@Nullable
private TelemetryDefinition telemetry;
@Nullable
private AccountsManager accounts;
/**
@@ -61,7 +58,6 @@ public final class Mapbox {
FileSource.initializeFileDirsPaths(appContext);
INSTANCE = new Mapbox(appContext, accessToken);
if (isAccessTokenValid(accessToken)) {
initializeTelemetry();
INSTANCE.accounts = new AccountsManager();
}
ConnectivityReceiver.instance(appContext);
@@ -92,15 +88,8 @@ public final class Mapbox {
validateMapbox();
INSTANCE.accessToken = accessToken;
// cleanup telemetry which is dependent on an access token
if (INSTANCE.telemetry != null) {
INSTANCE.telemetry.disableTelemetrySession();
INSTANCE.telemetry = null;
}
// initialize components dependent on a token
if (isAccessTokenValid(accessToken)) {
initializeTelemetry();
INSTANCE.accounts = new AccountsManager();
} else {
INSTANCE.accounts = null;
@@ -108,23 +97,6 @@ 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() {
if (!hasInstance() || INSTANCE.accounts == null) {
throw new MapboxConfigurationException(
"A valid access token parameter is required when using a Mapbox service."
+ "\nPlease see https://www.mapbox.com/help/create-api-access-token/ to learn how to create one."
+ "\nMore information in this guide https://www.mapbox.com/help/first-steps-android-sdk/#access-tokens."
+ "Currently provided token is: " + INSTANCE.accessToken);
}
return INSTANCE.accounts.getSkuToken();
}
/**
* Application context
*
@@ -159,29 +131,6 @@ public final class Mapbox {
return ConnectivityReceiver.instance(INSTANCE.context).isConnected();
}
/**
* Initializes telemetry
*/
private static void initializeTelemetry() {
try {
INSTANCE.telemetry = getModuleProvider().obtainTelemetry();
} catch (Exception exception) {
String message = "Error occurred while initializing telemetry";
Logger.e(TAG, message, exception);
MapStrictMode.strictModeViolation(message, exception);
}
}
/**
* Get an instance of Telemetry if initialised
*
* @return instance of telemetry
*/
@Nullable
public static TelemetryDefinition getTelemetry() {
return hasInstance() ? INSTANCE.telemetry : null;
}
/**
* Get the module provider
*

View File

@@ -3,7 +3,6 @@ package com.mapbox.mapboxsdk;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.http.HttpRequest;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
/**
* Injects concrete instances of configurable abstractions
@@ -18,15 +17,6 @@ public interface ModuleProvider {
@NonNull
HttpRequest createHttpRequest();
/**
* Get the concrete implementation of TelemetryDefinition
*
* @return a single instance of Telemetry
*/
@Nullable
TelemetryDefinition obtainTelemetry();
/**
* Get the concrete implementation of LibraryLoaderProvider
*

View File

@@ -3,10 +3,8 @@ package com.mapbox.mapboxsdk;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mapbox.mapboxsdk.http.HttpRequest;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.module.http.HttpRequestImpl;
import com.mapbox.mapboxsdk.module.loader.LibraryLoaderProviderImpl;
import com.mapbox.mapboxsdk.module.telemetry.TelemetryImpl;
public class ModuleProviderImpl implements ModuleProvider {
@@ -16,12 +14,6 @@ public class ModuleProviderImpl implements ModuleProvider {
return new HttpRequestImpl();
}
@Override
@Nullable
public TelemetryDefinition obtainTelemetry() {
return new TelemetryImpl();
}
@NonNull
@Override
public LibraryLoaderProvider createLibraryLoaderProvider() {

View File

@@ -9,9 +9,7 @@ public class Attribution {
private static final String OPENSTREETMAP = "OpenStreetMap";
private static final String OPENSTREETMAP_ABBR = "OSM";
static final String TELEMETRY = "Telemetry Settings";
static final String MAPBOX_URL = "https://www.mapbox.com/about/maps/";
static final String TELEMETRY_URL = "https://www.mapbox.com/telemetry/";
static final List<String> IMPROVE_MAP_URLS = new ArrayList<>();
static {

View File

@@ -27,16 +27,14 @@ public class AttributionParser {
private final String attributionData;
private final boolean withImproveMap;
private final boolean withCopyrightSign;
private final boolean withTelemetryAttribution;
private final boolean withMapboxAttribution;
AttributionParser(WeakReference<Context> context, String attributionData, boolean withImproveMap,
boolean withCopyrightSign, boolean withTelemetryAttribution, boolean withMapboxAttribution) {
boolean withCopyrightSign, boolean withMapboxAttribution) {
this.context = context;
this.attributionData = attributionData;
this.withImproveMap = withImproveMap;
this.withCopyrightSign = withCopyrightSign;
this.withTelemetryAttribution = withTelemetryAttribution;
this.withMapboxAttribution = withMapboxAttribution;
}
@@ -85,7 +83,6 @@ public class AttributionParser {
*/
protected void parse() {
parseAttributions();
addAdditionalAttributions();
}
/**
@@ -109,9 +106,6 @@ public class AttributionParser {
String url = urlSpan.getURL();
if (isUrlValid(url)) {
String anchor = parseAnchorValue(htmlBuilder, urlSpan);
if (isImproveThisMapAnchor(anchor)) {
anchor = translateImproveThisMapAnchor(anchor);
}
attributions.add(new Attribution(anchor, url));
}
}
@@ -136,20 +130,6 @@ public class AttributionParser {
return anchor.equals(IMPROVE_THIS_MAP);
}
/**
* Invoked to replace the english Improve this map with localized variant.
*
* @param anchor the anchor to be translated
* @return the translated anchor
*/
private String translateImproveThisMapAnchor(String anchor) {
Context context = this.context.get();
if (context != null) {
anchor = context.getString(R.string.mapbox_telemetryImproveMap);
}
return anchor;
}
/**
* Invoked to validate if an url is valid for the improve map configuration.
*
@@ -201,21 +181,6 @@ public class AttributionParser {
return anchor;
}
/**
* Invoked to manually add attributions
*/
private void addAdditionalAttributions() {
if (withTelemetryAttribution) {
Context context = this.context.get();
attributions.add(
new Attribution(
context != null ? context.getString(R.string.mapbox_telemetrySettings) : Attribution.TELEMETRY,
Attribution.TELEMETRY_URL
)
);
}
}
/**
* Convert a string to a spanned html representation.
*
@@ -236,15 +201,14 @@ public class AttributionParser {
* Builder to configure using an AttributionParser.
* <p>
* AttributionData, set with {@link #withAttributionData(String...)}, is the only required property to build
* the underlying AttributionParser. Other properties include trimming the copyright sign, adding telemetry
* attribution or hiding attribution as improve this map and Mapbox.
* the underlying AttributionParser. Other properties include trimming the copyright sign, hiding
* attribution as improve this map and Mapbox.
* </p>
*/
public static class Options {
private final WeakReference<Context> context;
private boolean withImproveMap = true;
private boolean withCopyrightSign = true;
private boolean withTelemetryAttribution = false;
private boolean withMapboxAttribution = true;
private String[] attributionDataStringArray;
@@ -270,12 +234,6 @@ public class AttributionParser {
return this;
}
@NonNull
public Options withTelemetryAttribution(boolean withTelemetryAttribution) {
this.withTelemetryAttribution = withTelemetryAttribution;
return this;
}
@NonNull
public Options withMapboxAttribution(boolean withMapboxAttribution) {
this.withMapboxAttribution = withMapboxAttribution;
@@ -294,7 +252,6 @@ public class AttributionParser {
fullAttributionString,
withImproveMap,
withCopyrightSign,
withTelemetryAttribution,
withMapboxAttribution
);
attributionParser.parse();

View File

@@ -37,12 +37,6 @@ public class MapboxConstants {
*/
public static final boolean DEFAULT_MEASURE_TILE_DOWNLOAD_ON = false;
public static final String KEY_PREFERENCE_SKU_TOKEN = "com.mapbox.mapboxsdk.accounts.skutoken";
public static final String KEY_META_DATA_MANAGE_SKU_TOKEN = "com.mapbox.ManageSkuToken";
public static final boolean DEFAULT_MANAGE_SKU_TOKEN = true;
/**
* Default value for font fallback for local ideograph fonts
*/

View File

@@ -1,14 +0,0 @@
package com.mapbox.mapboxsdk.constants;
@Deprecated
public class TelemetryConstants {
public static final String TWO_FINGER_TAP = "TwoFingerTap";
public static final String DOUBLE_TAP = "DoubleTap";
public static final String SINGLE_TAP = "SingleTap";
public static final String PAN = "Pan";
public static final String PINCH = "Pinch";
public static final String ROTATION = "Rotation";
public static final String PITCH = "Pitch";
}

View File

@@ -31,8 +31,6 @@ public class HttpRequestUrl {
// Only add SKU token to requests not tagged as "offline" usage.
if (offline) {
resourceUrl = resourceUrl + "offline=true";
} else {
resourceUrl = resourceUrl + "sku=" + Mapbox.getSkuToken();
}
}
return resourceUrl;

View File

@@ -14,12 +14,12 @@ import androidx.annotation.RequiresPermission;
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
import com.mapbox.android.core.location.LocationEngine;
import com.mapbox.android.core.location.LocationEngineCallback;
import com.mapbox.android.core.location.LocationEngineProvider;
import com.mapbox.android.core.location.LocationEngineRequest;
import com.mapbox.android.core.location.LocationEngineResult;
import com.mapbox.android.core.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.location.engine.LocationEngine;
import com.mapbox.mapboxsdk.location.engine.LocationEngineCallback;
import com.mapbox.mapboxsdk.location.engine.LocationEngineProvider;
import com.mapbox.mapboxsdk.location.engine.LocationEngineRequest;
import com.mapbox.mapboxsdk.location.engine.LocationEngineResult;
import com.mapbox.mapboxsdk.location.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdate;
@@ -77,10 +77,10 @@ import static com.mapbox.mapboxsdk.location.LocationComponentConstants.TRANSITIO
* <p>
* This component offers a default, built-in {@link LocationEngine} with some of the activation methods.
* This engine will be obtained by {@link LocationEngineProvider#getBestLocationEngine(Context, boolean)} which defaults
* to the {@link com.mapbox.android.core.location.MapboxFusedLocationEngineImpl}. If you'd like to utilize Google
* to the {@link com.mapbox.mapboxsdk.location.engine.MapboxFusedLocationEngineImpl}. If you'd like to utilize Google
* Play Services
* for more precise location updates, simply add the Google Play Location Services dependency in your build script.
* This will make the default engine the {@link com.mapbox.android.core.location.GoogleLocationEngineImpl} instead.
* This will make the default engine the {@link com.mapbox.mapboxsdk.location.engine.GoogleLocationEngineImpl} instead.
* After a custom engine is passed to the component, or the built-in is initialized,
* the location updates are going to be requested with the {@link LocationEngineRequest}, either a default one,
* or the one passed during the activation.

View File

@@ -5,8 +5,8 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mapbox.android.core.location.LocationEngine;
import com.mapbox.android.core.location.LocationEngineRequest;
import com.mapbox.mapboxsdk.location.engine.LocationEngine;
import com.mapbox.mapboxsdk.location.engine.LocationEngineRequest;
import com.mapbox.mapboxsdk.maps.Style;
/**

View File

@@ -0,0 +1,174 @@
package com.mapbox.mapboxsdk.location.engine;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.util.Log;
/**
* A location engine that uses core android.location and has no external dependencies
* https://developer.android.com/guide/topics/location/strategies.html
*/
class AndroidLocationEngineImpl implements LocationEngineImpl<LocationListener> {
private static final String TAG = "AndroidLocationEngine";
final LocationManager locationManager;
String currentProvider = LocationManager.PASSIVE_PROVIDER;
AndroidLocationEngineImpl(@NonNull Context context) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
@NonNull
@Override
public LocationListener createListener(LocationEngineCallback<LocationEngineResult> callback) {
return new AndroidLocationEngineCallbackTransport(callback);
}
@Override
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback)
throws SecurityException {
Location lastLocation = getLastLocationFor(currentProvider);
if (lastLocation != null) {
callback.onSuccess(LocationEngineResult.create(lastLocation));
return;
}
for (String provider : locationManager.getAllProviders()) {
lastLocation = getLastLocationFor(provider);
if (lastLocation != null) {
callback.onSuccess(LocationEngineResult.create(lastLocation));
return;
}
}
callback.onFailure(new Exception("Last location unavailable"));
}
@SuppressLint("MissingPermission")
Location getLastLocationFor(String provider) throws SecurityException {
Location location = null;
try {
location = locationManager.getLastKnownLocation(provider);
} catch (IllegalArgumentException iae) {
Log.e(TAG, iae.toString());
}
return location;
}
@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull LocationListener listener,
@Nullable Looper looper) throws SecurityException {
// Pick best provider only if user has not explicitly chosen passive mode
currentProvider = getBestProvider(request.getPriority());
locationManager.requestLocationUpdates(currentProvider, request.getInterval(), request.getDisplacement(),
listener, looper);
}
@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull PendingIntent pendingIntent) throws SecurityException {
// Pick best provider only if user has not explicitly chosen passive mode
currentProvider = getBestProvider(request.getPriority());
locationManager.requestLocationUpdates(currentProvider, request.getInterval(),
request.getDisplacement(), pendingIntent);
}
@SuppressLint("MissingPermission")
@Override
public void removeLocationUpdates(@NonNull LocationListener listener) {
if (listener != null) {
locationManager.removeUpdates(listener);
}
}
@Override
public void removeLocationUpdates(PendingIntent pendingIntent) {
if (pendingIntent != null) {
locationManager.removeUpdates(pendingIntent);
}
}
private String getBestProvider(int priority) {
String provider = null;
// Pick best provider only if user has not explicitly chosen passive mode
if (priority != LocationEngineRequest.PRIORITY_NO_POWER) {
provider = locationManager.getBestProvider(getCriteria(priority), true);
}
return provider != null ? provider : LocationManager.PASSIVE_PROVIDER;
}
@VisibleForTesting
static Criteria getCriteria(int priority) {
Criteria criteria = new Criteria();
criteria.setAccuracy(priorityToAccuracy(priority));
criteria.setCostAllowed(true);
criteria.setPowerRequirement(priorityToPowerRequirement(priority));
return criteria;
}
private static int priorityToAccuracy(int priority) {
switch (priority) {
case LocationEngineRequest.PRIORITY_HIGH_ACCURACY:
case LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY:
return Criteria.ACCURACY_FINE;
case LocationEngineRequest.PRIORITY_LOW_POWER:
case LocationEngineRequest.PRIORITY_NO_POWER:
default:
return Criteria.ACCURACY_COARSE;
}
}
private static int priorityToPowerRequirement(int priority) {
switch (priority) {
case LocationEngineRequest.PRIORITY_HIGH_ACCURACY:
return Criteria.POWER_HIGH;
case LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY:
return Criteria.POWER_MEDIUM;
case LocationEngineRequest.PRIORITY_LOW_POWER:
case LocationEngineRequest.PRIORITY_NO_POWER:
default:
return Criteria.POWER_LOW;
}
}
@VisibleForTesting
static final class AndroidLocationEngineCallbackTransport implements LocationListener {
private final LocationEngineCallback<LocationEngineResult> callback;
AndroidLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
this.callback = callback;
}
@Override
public void onLocationChanged(Location location) {
callback.onSuccess(LocationEngineResult.create(location));
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
// noop
}
@Override
public void onProviderEnabled(String s) {
// noop
}
@Override
public void onProviderDisabled(String s) {
callback.onFailure(new Exception("Current provider disabled"));
}
}
}

View File

@@ -0,0 +1,145 @@
package com.mapbox.mapboxsdk.location.engine;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.location.Location;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import java.util.Collections;
import java.util.List;
/**
* Wraps implementation of Fused Location Provider
*/
class GoogleLocationEngineImpl implements LocationEngineImpl<LocationCallback> {
private final FusedLocationProviderClient fusedLocationProviderClient;
@VisibleForTesting
GoogleLocationEngineImpl(FusedLocationProviderClient fusedLocationProviderClient) {
this.fusedLocationProviderClient = fusedLocationProviderClient;
}
GoogleLocationEngineImpl(@NonNull Context context) {
this.fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
}
@NonNull
@Override
public LocationCallback createListener(LocationEngineCallback<LocationEngineResult> callback) {
return new GoogleLocationEngineCallbackTransport(callback);
}
@SuppressLint("MissingPermission")
@Override
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback)
throws SecurityException {
GoogleLastLocationEngineCallbackTransport transport =
new GoogleLastLocationEngineCallbackTransport(callback);
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(transport).addOnFailureListener(transport);
}
@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull LocationCallback listener,
@Nullable Looper looper) throws SecurityException {
fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), listener, looper);
}
@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull PendingIntent pendingIntent) throws SecurityException {
fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), pendingIntent);
}
@Override
public void removeLocationUpdates(@NonNull LocationCallback listener) {
if (listener != null) {
fusedLocationProviderClient.removeLocationUpdates(listener);
}
}
@Override
public void removeLocationUpdates(PendingIntent pendingIntent) {
if (pendingIntent != null) {
fusedLocationProviderClient.removeLocationUpdates(pendingIntent);
}
}
private static LocationRequest toGMSLocationRequest(LocationEngineRequest request) {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(request.getInterval());
locationRequest.setFastestInterval(request.getFastestInterval());
locationRequest.setSmallestDisplacement(request.getDisplacement());
locationRequest.setMaxWaitTime(request.getMaxWaitTime());
locationRequest.setPriority(toGMSLocationPriority(request.getPriority()));
return locationRequest;
}
private static int toGMSLocationPriority(int enginePriority) {
switch (enginePriority) {
case LocationEngineRequest.PRIORITY_HIGH_ACCURACY:
return LocationRequest.PRIORITY_HIGH_ACCURACY;
case LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY:
return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
case LocationEngineRequest.PRIORITY_LOW_POWER:
return LocationRequest.PRIORITY_LOW_POWER;
case LocationEngineRequest.PRIORITY_NO_POWER:
default:
return LocationRequest.PRIORITY_NO_POWER;
}
}
private static final class GoogleLocationEngineCallbackTransport extends LocationCallback {
private final LocationEngineCallback<LocationEngineResult> callback;
GoogleLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
this.callback = callback;
}
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
List<Location> locations = locationResult.getLocations();
if (!locations.isEmpty()) {
callback.onSuccess(LocationEngineResult.create(locations));
} else {
callback.onFailure(new Exception("Unavailable location"));
}
}
}
@VisibleForTesting
static final class GoogleLastLocationEngineCallbackTransport
implements OnSuccessListener<Location>, OnFailureListener {
private final LocationEngineCallback<LocationEngineResult> callback;
GoogleLastLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
this.callback = callback;
}
@Override
public void onSuccess(Location location) {
callback.onSuccess(location != null ? LocationEngineResult.create(location) :
LocationEngineResult.create(Collections.<Location>emptyList()));
}
@Override
public void onFailure(@NonNull Exception e) {
callback.onFailure(e);
}
}
}

View File

@@ -0,0 +1,81 @@
package com.mapbox.mapboxsdk.location.engine;
import android.app.PendingIntent;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
/**
* Generic location engine interface wrapper for the location providers.
* Default providers bundled with Mapbox location library:
* Android location provider and Google Play Services fused location provider
*
* @since 1.0.0
*/
public interface LocationEngine {
/**
* Returns the most recent location currently available.
* <p>
* If a location is not available, which should happen very rarely, null will be returned.
*
* @param callback {@link LocationEngineCallback} for the location result {@link LocationEngineResult}.
* @throws SecurityException if permission is not granted to access location services.
* @since 1.0.0
*/
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback) throws SecurityException;
/**
* Requests location updates with a callback on the specified Looper thread.
*
* @param request {@link LocationEngineRequest} for the updates.
* @param callback {@link LocationEngineCallback} for the location result {@link LocationEngineResult}.
* @param looper The Looper object whose message queue will be used to implement the callback mechanism,
* or null to invoke callbacks on the main thread.
* @throws SecurityException if permission is not granted to access location services.
* @since 1.0.0
*/
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull LocationEngineCallback<LocationEngineResult> callback,
@Nullable Looper looper) throws SecurityException;
/**
* Requests location updates with callback on the specified PendingIntent.
*
* @param request {@link LocationEngineRequest} for the updates.
* @param pendingIntent {@link PendingIntent} for the location result {@link LocationEngineResult}.
* @throws SecurityException if permission is not granted to access location services.
* @since 1.1.0
*/
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
void requestLocationUpdates(@NonNull LocationEngineRequest request,
PendingIntent pendingIntent) throws SecurityException;
/**
* Removes location updates for the given location engine callback.
* <p>
* It is recommended to remove location requests when the activity is in a paused or
* stopped state, doing so helps battery performance.
*
* @param callback {@link LocationEngineCallback} to remove.
* @since 1.0.0
*/
void removeLocationUpdates(@NonNull LocationEngineCallback<LocationEngineResult> callback);
/**
* Removes location updates for the given pending intent.
* <p>
* It is recommended to remove location requests when the activity is in a paused or
* stopped state, doing so helps battery performance.
*
* @param pendingIntent {@link PendingIntent} to remove.
* @since 1.1.0
*/
void removeLocationUpdates(PendingIntent pendingIntent);
}

View File

@@ -0,0 +1,25 @@
package com.mapbox.mapboxsdk.location.engine;
import androidx.annotation.NonNull;
/**
* Invoked for asynchronous notifications when new data
* from engine becomes available.
*
* @param <T> Successful updated data type
*/
public interface LocationEngineCallback<T> {
/**
* Invoked when new data available.
*
* @param result updated data.
*/
void onSuccess(T result);
/**
* Invoked when engine exception occurs.
*
* @param exception {@link Exception}
*/
void onFailure(@NonNull Exception exception);
}

View File

@@ -0,0 +1,28 @@
package com.mapbox.mapboxsdk.location.engine;
import android.app.PendingIntent;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Internal location engine implementation interface.
*
* @param <T> location listener object type
*/
interface LocationEngineImpl<T> {
@NonNull
T createListener(LocationEngineCallback<LocationEngineResult> callback);
void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback) throws SecurityException;
void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull T listener, @Nullable Looper looper) throws SecurityException;
void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull PendingIntent pendingIntent) throws SecurityException;
void removeLocationUpdates(T listener);
void removeLocationUpdates(PendingIntent pendingIntent);
}

View File

@@ -0,0 +1,60 @@
package com.mapbox.mapboxsdk.location.engine;
import android.content.Context;
import androidx.annotation.NonNull;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import static com.mapbox.mapboxsdk.location.engine.Utils.checkNotNull;
import static com.mapbox.mapboxsdk.location.engine.Utils.isOnClasspath;
/**
* The main entry point for location engine integration.
*/
public final class LocationEngineProvider {
private static final String GOOGLE_LOCATION_SERVICES = "com.google.android.gms.location.LocationServices";
private static final String GOOGLE_API_AVAILABILITY = "com.google.android.gms.common.GoogleApiAvailability";
private LocationEngineProvider() {
// prevent instantiation
}
/**
* Returns instance to the best location engine, given the included libraries.
*
* @param context {@link Context}.
* @param background true if background optimized engine is desired (note: parameter deprecated)
* @return a unique instance of {@link LocationEngine} every time method is called.
* @since 1.0.0
*/
@NonNull
@Deprecated
public static LocationEngine getBestLocationEngine(@NonNull Context context, boolean background) {
return getBestLocationEngine(context);
}
/**
* Returns instance to the best location engine, given the included libraries.
*
* @param context {@link Context}.
* @return a unique instance of {@link LocationEngine} every time method is called.
* @since 1.1.0
*/
@NonNull
public static LocationEngine getBestLocationEngine(@NonNull Context context) {
checkNotNull(context, "context == null");
boolean hasGoogleLocationServices = isOnClasspath(GOOGLE_LOCATION_SERVICES);
if (isOnClasspath(GOOGLE_API_AVAILABILITY)) {
// Check Google Play services APK is available and up-to-date on this device
hasGoogleLocationServices &= GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
== ConnectionResult.SUCCESS;
}
return getLocationEngine(context, hasGoogleLocationServices);
}
private static LocationEngine getLocationEngine(Context context, boolean isGoogle) {
return isGoogle ? new LocationEngineProxy<>(new GoogleLocationEngineImpl(context.getApplicationContext())) :
new LocationEngineProxy<>(new MapboxFusedLocationEngineImpl(context.getApplicationContext()));
}
}

View File

@@ -0,0 +1,79 @@
package com.mapbox.mapboxsdk.location.engine;
import android.app.PendingIntent;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.mapbox.mapboxsdk.location.engine.Utils.checkNotNull;
class LocationEngineProxy<T> implements LocationEngine {
private final LocationEngineImpl<T> locationEngineImpl;
private Map<LocationEngineCallback<LocationEngineResult>, T> listeners;
LocationEngineProxy(LocationEngineImpl<T> locationEngineImpl) {
this.locationEngineImpl = locationEngineImpl;
}
@Override
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback) throws SecurityException {
checkNotNull(callback, "callback == null");
locationEngineImpl.getLastLocation(callback);
}
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull LocationEngineCallback<LocationEngineResult> callback,
@Nullable Looper looper) throws SecurityException {
checkNotNull(request, "request == null");
checkNotNull(callback, "callback == null");
locationEngineImpl.requestLocationUpdates(request, getListener(callback),
looper == null ? Looper.getMainLooper() : looper);
}
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
PendingIntent pendingIntent) throws SecurityException {
checkNotNull(request, "request == null");
locationEngineImpl.requestLocationUpdates(request, pendingIntent);
}
@Override
public void removeLocationUpdates(@NonNull LocationEngineCallback<LocationEngineResult> callback) {
checkNotNull(callback, "callback == null");
locationEngineImpl.removeLocationUpdates(removeListener(callback));
}
@Override
public void removeLocationUpdates(PendingIntent pendingIntent) {
locationEngineImpl.removeLocationUpdates(pendingIntent);
}
@VisibleForTesting
int getListenersCount() {
return listeners != null ? listeners.size() : 0;
}
@VisibleForTesting
T getListener(@NonNull LocationEngineCallback<LocationEngineResult> callback) {
if (listeners == null) {
listeners = new ConcurrentHashMap<>();
}
T listener = listeners.get(callback);
if (listener == null) {
listener = locationEngineImpl.createListener(callback);
}
listeners.put(callback, listener);
return listener;
}
@VisibleForTesting
T removeListener(@NonNull LocationEngineCallback<LocationEngineResult> callback) {
return listeners != null ? listeners.remove(callback) : null;
}
}

View File

@@ -0,0 +1,242 @@
package com.mapbox.mapboxsdk.location.engine;
/**
* Data model that contains parameters for location
* engine requests.
*
* @since 1.0.0
*/
public class LocationEngineRequest {
/**
* Used with {@link Builder#setPriority(int)} to request
* the most accurate location.
*
* @since 1.0.0
*/
public static final int PRIORITY_HIGH_ACCURACY = 0;
/**
* Used with {@link Builder#setPriority(int)} to request
* coarse location that is battery optimized.
*
* @since 1.0.0
*/
public static final int PRIORITY_BALANCED_POWER_ACCURACY = 1;
/**
* Used with {@link Builder#setPriority(int)} to request
* coarse ~ 10 km accuracy location.
*
* @since 1.0.0
*/
public static final int PRIORITY_LOW_POWER = 2;
/**
* Used with {@link Builder#setPriority(int)} to request
* passive location: no locations will be returned unless a different client
* has requested location updates.
*
* @since 1.0.0
*/
public static final int PRIORITY_NO_POWER = 3;
private final long interval;
private final int priority;
private final float displacement;
private final long maxWaitTime;
private final long fastestInterval;
private LocationEngineRequest(Builder builder) {
this.interval = builder.interval;
this.priority = builder.priority;
this.displacement = builder.displacement;
this.maxWaitTime = builder.maxWaitTime;
this.fastestInterval = builder.fastestInterval;
}
/**
* Returns desired interval between location updates
* in milliseconds.
*
* @return desired interval in milliseconds.
* @since 1.0.0
*/
public long getInterval() {
return interval;
}
/**
* Returns desired quality of the request.
*
* @return accuracy constant.
* @since 1.0.0
*/
public int getPriority() {
return priority;
}
/**
* Returns distance between location updates.
*
* @return distance between location updates in meters.
* @since 1.0.0
*/
public float getDisplacement() {
return displacement;
}
/**
* Returns maximum wait time in milliseconds for location updates.
*
* @return maximum wait time in milliseconds.
* @since 1.0.0
*/
public long getMaxWaitTime() {
return maxWaitTime;
}
/**
* Returns fastest interval in milliseconds for location updates.
*
* @return fastest interval in milliseconds.
* @since 1.0.0
*/
public long getFastestInterval() {
return fastestInterval;
}
/**
* Compares this LocationEngineRequest to the specified object.
*
* @param o locationEngineRequest to compare to.
* @return true when the type and values are equal, false otherwise.
* @since 3.1.1
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LocationEngineRequest that = (LocationEngineRequest) o;
if (interval != that.interval) {
return false;
}
if (priority != that.priority) {
return false;
}
if (Float.compare(that.displacement, displacement) != 0) {
return false;
}
if (maxWaitTime != that.maxWaitTime) {
return false;
}
return fastestInterval == that.fastestInterval;
}
/**
* Returns a hash code for this object.
*
* @return integer hash of the values.
* @since 3.1.1
*/
@Override
public int hashCode() {
int result = (int) (interval ^ (interval >>> 32));
result = 31 * result + priority;
result = 31 * result + (displacement != +0.0f ? Float.floatToIntBits(displacement) : 0);
result = 31 * result + (int) (maxWaitTime ^ (maxWaitTime >>> 32));
result = 31 * result + (int) (fastestInterval ^ (fastestInterval >>> 32));
return result;
}
public static final class Builder {
private final long interval;
private int priority;
private float displacement;
private long maxWaitTime;
private long fastestInterval;
/**
* Default builder constructor.
*
* @param interval default interval between location updates
* @since 1.0.0
*/
public Builder(long interval) {
this.interval = interval;
this.priority = PRIORITY_HIGH_ACCURACY;
this.displacement = 0.0f;
this.maxWaitTime = 0L;
this.fastestInterval = 0L;
}
/**
* Set priority for request.
* Use priority constant: {@link #PRIORITY_HIGH_ACCURACY}
*
* @param priority constant
* @return reference to builder
* @since 1.0.0
*/
public Builder setPriority(int priority) {
this.priority = priority;
return this;
}
/**
* Set distance between location updates.
*
* @param displacement distance between locations in meters.
* @return reference to builder
* @since 1.0.0
*/
public Builder setDisplacement(float displacement) {
this.displacement = displacement;
return this;
}
/**
* Sets the maximum wait time in milliseconds for location updates.
* <p>
* Locations determined at intervals but delivered in batch based on
* wait time. Batching is not supported by all engines.
*
* @param maxWaitTime wait time in milliseconds.
* @return reference to builder
* @since 1.0.0
*/
public Builder setMaxWaitTime(long maxWaitTime) {
this.maxWaitTime = maxWaitTime;
return this;
}
/**
* Sets the fastest interval in milliseconds for location updates.
*
* @param interval fastest interval in milliseconds.
* @return reference to builder
* @since 1.0.0
*/
public Builder setFastestInterval(long interval) {
this.fastestInterval = interval;
return this;
}
/**
* Builds request object.
*
* @return instance of location request.
* @since 1.0.0
*/
public LocationEngineRequest build() {
return new LocationEngineRequest(this);
}
}
}

View File

@@ -0,0 +1,119 @@
package com.mapbox.mapboxsdk.location.engine;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.location.LocationResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.mapbox.mapboxsdk.location.engine.Utils.isOnClasspath;
/**
* A wrapper class representing location result from the location engine.
* <p>
* TODO: Override default equals(), hashCode() and toString()
*
* @since 1.0.0
*/
public final class LocationEngineResult {
private static final String GOOGLE_PLAY_LOCATION_RESULT = "com.google.android.gms.location.LocationResult";
private final List<Location> locations;
private LocationEngineResult(List<Location> locations) {
this.locations = Collections.unmodifiableList(locations);
}
/**
* Creates {@link LocationEngineResult} instance for location.
*
* @param location default location added to the result.
* @return instance of the new location result.
* @since 1.0.0
*/
@NonNull
public static LocationEngineResult create(@Nullable Location location) {
List<Location> locations = new ArrayList<>();
if (location != null) {
locations.add(location);
}
return new LocationEngineResult(locations);
}
/**
* Creates {@link LocationEngineResult} instance for given list of locations.
*
* @param locations list of locations.
* @return instance of the new location result.
* @since 1.0.0
*/
@NonNull
public static LocationEngineResult create(@Nullable List<Location> locations) {
if (locations != null) {
List<Location> locationsList = new ArrayList<>(locations);
locationsList.removeAll(Collections.singleton(null));
return new LocationEngineResult(locationsList);
}
return new LocationEngineResult(Collections.<Location>emptyList());
}
/**
* Returns most recent location available in this result.
*
* @return the most recent location {@link Location} or null.
* @since 1.0.0
*/
@Nullable
public Location getLastLocation() {
return locations.isEmpty() ? null : locations.get(0);
}
/**
* Returns locations computed, ordered from oldest to newest.
*
* @return ordered list of locations.
* @since 1.0.0
*/
public List<Location> getLocations() {
return Collections.unmodifiableList(locations);
}
/**
* Extracts location result from intent object
*
* @param intent valid intent object
* @return location result.
* @since 1.1.0
*/
@Nullable
public static LocationEngineResult extractResult(Intent intent) {
LocationEngineResult result = null;
if (isOnClasspath(GOOGLE_PLAY_LOCATION_RESULT)) {
result = extractGooglePlayResult(intent);
}
return result == null ? extractAndroidResult(intent) : result;
}
private static LocationEngineResult extractGooglePlayResult(Intent intent) {
LocationResult result = LocationResult.extractResult(intent);
return result != null ? LocationEngineResult.create(result.getLocations()) : null;
}
private static LocationEngineResult extractAndroidResult(Intent intent) {
return !hasResult(intent) ? null :
LocationEngineResult.create((Location) intent.getExtras()
.getParcelable(LocationManager.KEY_LOCATION_CHANGED));
}
private static boolean hasResult(Intent intent) {
return intent != null && intent.hasExtra(LocationManager.KEY_LOCATION_CHANGED);
}
}

View File

@@ -0,0 +1,136 @@
package com.mapbox.mapboxsdk.location.engine;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.Log;
import static com.mapbox.mapboxsdk.location.engine.Utils.isBetterLocation;
/**
* Mapbox replacement for Google Play Services Fused Location Client
* <p>
* Note: fusion will not work in background mode.
*/
class MapboxFusedLocationEngineImpl extends AndroidLocationEngineImpl {
private static final String TAG = "MapboxLocationEngine";
MapboxFusedLocationEngineImpl(@NonNull Context context) {
super(context);
}
@NonNull
@Override
public LocationListener createListener(LocationEngineCallback<LocationEngineResult> callback) {
return new MapboxLocationEngineCallbackTransport(callback);
}
@Override
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback) throws SecurityException {
Location bestLastLocation = getBestLastLocation();
if (bestLastLocation != null) {
callback.onSuccess(LocationEngineResult.create(bestLastLocation));
} else {
callback.onFailure(new Exception("Last location unavailable"));
}
}
@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull LocationListener listener,
@Nullable Looper looper) throws SecurityException {
super.requestLocationUpdates(request, listener, looper);
// Start network provider along with gps
if (shouldStartNetworkProvider(request.getPriority())) {
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
request.getInterval(), request.getDisplacement(),
listener, looper);
} catch (IllegalArgumentException iae) {
iae.printStackTrace();
}
}
}
@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull PendingIntent pendingIntent) throws SecurityException {
super.requestLocationUpdates(request, pendingIntent);
// Start network provider along with gps
if (shouldStartNetworkProvider(request.getPriority())) {
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, request.getInterval(),
request.getDisplacement(), pendingIntent);
} catch (IllegalArgumentException iae) {
iae.printStackTrace();
}
}
}
private Location getBestLastLocation() {
Location bestLastLocation = null;
for (String provider : locationManager.getAllProviders()) {
Location location = getLastLocationFor(provider);
if (location == null) {
continue;
}
if (isBetterLocation(location, bestLastLocation)) {
bestLastLocation = location;
}
}
return bestLastLocation;
}
private boolean shouldStartNetworkProvider(int priority) {
return (priority == LocationEngineRequest.PRIORITY_HIGH_ACCURACY
|| priority == LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY)
&& currentProvider.equals(LocationManager.GPS_PROVIDER);
}
private static final class MapboxLocationEngineCallbackTransport implements LocationListener {
private final LocationEngineCallback<LocationEngineResult> callback;
private Location currentBestLocation;
MapboxLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
this.callback = callback;
}
@Override
public void onLocationChanged(Location location) {
if (isBetterLocation(location, currentBestLocation)) {
currentBestLocation = location;
}
if (callback != null) {
callback.onSuccess(LocationEngineResult.create(currentBestLocation));
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(TAG, "onStatusChanged: " + provider);
}
@Override
public void onProviderEnabled(String provider) {
Log.d(TAG, "onProviderEnabled: " + provider);
}
@Override
public void onProviderDisabled(String provider) {
Log.d(TAG, "onProviderDisabled: " + provider);
}
}
}

View File

@@ -0,0 +1,105 @@
package com.mapbox.mapboxsdk.location.engine;
import android.location.Location;
import androidx.annotation.Nullable;
final class Utils {
private static final int TWO_MINUTES = 1000 * 60 * 2;
private static final int ACCURACY_THRESHOLD_METERS = 200;
private Utils() {
// Prevent instantiation
}
/**
* Ensures that an object reference passed as a parameter to the calling method is not null.
*
* @param reference object reference.
* @param message exception message to use if check fails.
* @param <T> object type.
* @return validated non-null reference.
*/
static <T> T checkNotNull(@Nullable T reference, String message) {
if (reference == null) {
throw new NullPointerException(message);
}
return reference;
}
/**
* Checks if class is on class path
* @param className of the class to check.
* @return true if class in on class path, false otherwise.
*/
static boolean isOnClasspath(String className) {
boolean isOnClassPath = true;
try {
Class.forName(className);
} catch (ClassNotFoundException exception) {
isOnClassPath = false;
}
return isOnClassPath;
}
/**
* Determines whether one Location reading is better than the current Location fix
* <p>
* (c) https://developer.android.com/guide/topics/location/strategies
*
* @param location The new Location that you want to evaluate
* @param currentBestLocation The current Location fix, to which you want to compare the new one
*/
static boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > ACCURACY_THRESHOLD_METERS;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
/**
* Checks whether two providers are the same
* <p>
* (c) https://developer.android.com/guide/topics/location/strategies
*/
private static boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
}

View File

@@ -0,0 +1,14 @@
package com.mapbox.mapboxsdk.location.permissions;
import java.util.List;
/**
* Callback used in PermissionsManager
*/
public interface PermissionsListener {
void onExplanationNeeded(List<String> permissionsToExplain);
void onPermissionResult(boolean granted);
}

View File

@@ -0,0 +1,152 @@
package com.mapbox.mapboxsdk.location.permissions;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Helps request permissions at runtime.
*/
public class PermissionsManager {
private static final String LOG_TAG = "PermissionsManager";
private static final String COARSE_LOCATION_PERMISSION = Manifest.permission.ACCESS_COARSE_LOCATION;
private static final String FINE_LOCATION_PERMISSION = Manifest.permission.ACCESS_FINE_LOCATION;
private static final String BACKGROUND_LOCATION_PERMISSION = "android.permission.ACCESS_BACKGROUND_LOCATION";
private final int REQUEST_PERMISSIONS_CODE = 0;
private PermissionsListener listener;
public PermissionsManager(PermissionsListener listener) {
this.listener = listener;
}
public PermissionsListener getListener() {
return listener;
}
public void setListener(PermissionsListener listener) {
this.listener = listener;
}
private static boolean isPermissionGranted(Context context, String permission) {
return ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED;
}
private static boolean isCoarseLocationPermissionGranted(Context context) {
return isPermissionGranted(context, COARSE_LOCATION_PERMISSION);
}
private static boolean isFineLocationPermissionGranted(Context context) {
return isPermissionGranted(context, FINE_LOCATION_PERMISSION);
}
public static boolean isBackgroundLocationPermissionGranted(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return isPermissionGranted(context, BACKGROUND_LOCATION_PERMISSION);
}
return areLocationPermissionsGranted(context);
}
public static boolean areLocationPermissionsGranted(Context context) {
return isCoarseLocationPermissionGranted(context)
|| isFineLocationPermissionGranted(context);
}
public static boolean areRuntimePermissionsRequired() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
public void requestLocationPermissions(Activity activity) {
try {
PackageInfo packageInfo = activity.getPackageManager().getPackageInfo(
activity.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] requestedPermissions = packageInfo.requestedPermissions;
if (requestedPermissions != null) {
List<String> permissionList = Arrays.asList(requestedPermissions);
boolean fineLocPermission = permissionList.contains(FINE_LOCATION_PERMISSION);
boolean coarseLocPermission = permissionList.contains(COARSE_LOCATION_PERMISSION);
boolean backgroundLocPermission = permissionList.contains(BACKGROUND_LOCATION_PERMISSION);
// Request location permissions
if (fineLocPermission) {
requestLocationPermissions(activity, true, backgroundLocPermission);
} else if (coarseLocPermission) {
requestLocationPermissions(activity, false, backgroundLocPermission);
} else {
Log.w(LOG_TAG, "Location permissions are missing");
}
}
} catch (Exception exception) {
Log.w(LOG_TAG, exception.getMessage());
}
}
private void requestLocationPermissions(Activity activity, boolean requestFineLocation,
boolean requestBackgroundLocation) {
List<String> permissions = new ArrayList<>();
if (requestFineLocation) {
permissions.add(FINE_LOCATION_PERMISSION);
} else {
permissions.add(COARSE_LOCATION_PERMISSION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && requestBackgroundLocation) {
permissions.add(BACKGROUND_LOCATION_PERMISSION);
}
requestPermissions(activity, permissions.toArray(new String[permissions.size()]));
}
private void requestPermissions(Activity activity, String[] permissions) {
ArrayList<String> permissionsToExplain = new ArrayList<>();
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
permissionsToExplain.add(permission);
}
}
if (listener != null && permissionsToExplain.size() > 0) {
// The developer should show an explanation to the user asynchronously
listener.onExplanationNeeded(permissionsToExplain);
}
ActivityCompat.requestPermissions(activity, permissions, REQUEST_PERMISSIONS_CODE);
}
/**
* You should call this method from your activity onRequestPermissionsResult.
*
* @param requestCode The request code passed in requestPermissions(android.app.Activity, String[], int)
* @param permissions The requested permissions. Never null.
* @param grantResults The grant results for the corresponding permissions which is either
* PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
*/
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_PERMISSIONS_CODE:
if (listener != null) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
listener.onPermissionResult(granted);
}
break;
default:
// Ignored
}
}
}

View File

@@ -34,7 +34,6 @@ import java.util.regex.Pattern;
* <p>
* When the user clicks the attribution icon, {@link AttributionDialogManager#onClick(View)} will be invoked.
* An attribution dialog will be shown to the user with contents based on the attributions found in the map style.
* Additionally an telemetry option item is shown to configure telemetry settings.
* </p>
*/
public class AttributionDialogManager implements View.OnClickListener, DialogInterface.OnClickListener {
@@ -87,14 +86,10 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt
return titles.toArray(new String[titles.size()]);
}
// Called when someone selects an attribution or telemetry settings from the dialog
// Called when someone selects an attribution from the dialog
@Override
public void onClick(DialogInterface dialog, int which) {
if (isLatestEntry(which)) {
showTelemetryDialog();
} else {
showMapAttributionWebPage(which);
}
showMapAttributionWebPage(which);
}
public void onStop() {
@@ -107,40 +102,6 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt
return attributionKeyIndex == getAttributionTitles().length - 1;
}
private void showTelemetryDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.mapbox_attributionTelemetryTitle);
builder.setMessage(R.string.mapbox_attributionTelemetryMessage);
builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
telemetry.setUserTelemetryRequestState(true);
}
dialog.cancel();
}
});
builder.setNeutralButton(R.string.mapbox_attributionTelemetryNeutral, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
showWebPage(context.getResources().getString(R.string.mapbox_telemetryLink));
dialog.cancel();
}
});
builder.setNegativeButton(R.string.mapbox_attributionTelemetryNegative, new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
telemetry.setUserTelemetryRequestState(false);
}
dialog.cancel();
}
});
builder.show();
}
private void showMapAttributionWebPage(int which) {
Attribution[] attributions = attributionSet.toArray(new Attribution[attributionSet.size()]);
String url = attributions[which].getUrl();
@@ -236,7 +197,6 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt
return new AttributionParser.Options(context)
.withCopyrightSign(true)
.withImproveMap(true)
.withTelemetryAttribution(true)
.withAttributionData(attributions.toArray(new String[attributions.size()]))
.build().getAttributions();
}

View File

@@ -297,12 +297,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
*/
@UiThread
public void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) {
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
telemetry.onAppUserTurnstileEvent();
}
} else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) {
if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) {
this.savedInstanceState = savedInstanceState;
}
}

View File

@@ -1,66 +0,0 @@
package com.mapbox.mapboxsdk.maps;
import android.os.Bundle;
import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition;
/**
* Definition of TelemetryImpl collection.
* Use{@link TelemetryDefinition} for posting the event.
*/
public interface TelemetryDefinition {
/**
* Register the app user turnstile event
*/
void onAppUserTurnstileEvent();
/**
* Register an end-user gesture interaction event.
*
* @param eventType type of gesture event occurred
* @param latitude the latitude value of the gesture focal point
* @param longitude the longitude value of the gesture focal point
* @param zoom current zoom of the map
* @deprecated since 7.5.0, this event is no longer supported
*/
@Deprecated
void onGestureInteraction(String eventType, double latitude, double longitude, double zoom);
/**
* Set the end-user selected state to participate or opt-out in telemetry collection.
*/
void setUserTelemetryRequestState(boolean enabled);
/**
* Disables a started telemetry service for this session only.
*/
void disableTelemetrySession();
/**
* Set the end-user selected state to participate or opt-out in telemetry collection.
*/
void setDebugLoggingEnabled(boolean debugLoggingEnabled);
/**
* Set the telemetry rotation session id interval
*
* @param interval the selected session interval
* @return true if rotation session id was updated
*/
boolean setSessionIdRotationInterval(int interval);
/**
* Register an end-user offline download event.
*
* @param offlineDefinition the offline region definition
*/
void onCreateOfflineRegion(OfflineRegionDefinition offlineDefinition);
/**
* Register a performance event.
*
* @param data performance event data
*/
void onPerformanceEvent(Bundle data);
}

View File

@@ -1,39 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.annotation.SuppressLint;
import android.os.Parcel;
import com.mapbox.android.telemetry.Event;
/**
* Base event class for telemetry events.
*/
@SuppressLint("ParcelCreator")
abstract class MapBaseEvent extends Event {
private final String event;
private final String created;
MapBaseEvent(PhoneState phoneState) {
this.event = getEventName();
this.created = phoneState.getCreated();
}
abstract String getEventName();
String getEvent() {
return event;
}
String getCreated() {
return created;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}

View File

@@ -1,57 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.os.Bundle;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import com.mapbox.android.telemetry.TelemetryUtils;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
class MapEventFactory {
static MapLoadEvent buildMapLoadEvent(@NonNull PhoneState phoneState) {
String userId = TelemetryUtils.retrieveVendorId();
return new MapLoadEvent(userId, phoneState);
}
static OfflineDownloadStartEvent buildOfflineDownloadStartEvent(PhoneState phoneState,
String shapeForOfflineRegion,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double minZoom,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double maxZoom,
String styleURL) {
OfflineDownloadStartEvent offlineEvent =
new OfflineDownloadStartEvent(phoneState, shapeForOfflineRegion, minZoom, maxZoom);
offlineEvent.setStyleURL(styleURL);
return offlineEvent;
}
static OfflineDownloadEndEvent buildOfflineDownloadCompleteEvent(PhoneState phoneState,
String shapeForOfflineRegion,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double minZoom,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double maxZoom,
String styleURL,
long sizeOfResourcesCompleted,
long numberOfTilesCompleted,
@OfflineRegion.DownloadState int state) {
OfflineDownloadEndEvent offlineEvent =
new OfflineDownloadEndEvent(phoneState, shapeForOfflineRegion, minZoom, maxZoom);
offlineEvent.setStyleURL(styleURL);
offlineEvent.setSizeOfResourcesCompleted(sizeOfResourcesCompleted);
offlineEvent.setNumberOfTilesCompleted(numberOfTilesCompleted);
offlineEvent.setState(state);
return offlineEvent;
}
static PerformanceEvent buildPerformanceEvent(@NonNull PhoneState phoneState, @NonNull String sessionId,
@NonNull Bundle data) {
return new PerformanceEvent(phoneState, sessionId, data);
}
}

View File

@@ -1,179 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.annotation.SuppressLint;
import android.os.Build;
import com.mapbox.mapboxsdk.BuildConfig;
/**
* Event will be sent while map is loaded.
*/
@SuppressLint("ParcelCreator")
class MapLoadEvent extends MapBaseEvent {
private static final String EVENT_NAME = "map.load";
private final String operatingSystem = "Android - " + Build.VERSION.RELEASE;
private final String sdkIdentifier = BuildConfig.MAPBOX_SDK_IDENTIFIER;
private final String sdkVersion = BuildConfig.MAPBOX_SDK_VERSION;
private final String model = Build.MODEL;
private final String userId;
private final String carrier;
private final String cellularNetworkType;
private final String orientation;
private final float resolution;
private final float accessibilityFontScale;
private final int batteryLevel;
private final boolean pluggedIn;
private final boolean wifi;
MapLoadEvent(String userId, PhoneState phoneState) {
super(phoneState);
this.userId = userId;
this.batteryLevel = phoneState.getBatteryLevel();
this.pluggedIn = phoneState.isPluggedIn();
this.cellularNetworkType = phoneState.getCellularNetworkType();
this.carrier = phoneState.getCarrier();
this.resolution = phoneState.getResolution();
this.accessibilityFontScale = phoneState.getAccessibilityFontScale();
this.wifi = phoneState.isWifi();
this.orientation = phoneState.getOrientation();
}
@Override
String getEventName() {
return EVENT_NAME;
}
String getOperatingSystem() {
return operatingSystem;
}
String getSdkIdentifier() {
return sdkIdentifier;
}
String getSdkVersion() {
return sdkVersion;
}
String getModel() {
return model;
}
String getUserId() {
return userId;
}
String getCarrier() {
return carrier;
}
String getCellularNetworkType() {
return cellularNetworkType;
}
String getOrientation() {
return orientation;
}
float getResolution() {
return resolution;
}
float getAccessibilityFontScale() {
return accessibilityFontScale;
}
int getBatteryLevel() {
return batteryLevel;
}
boolean isPluggedIn() {
return pluggedIn;
}
boolean isWifi() {
return wifi;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MapLoadEvent that = (MapLoadEvent) o;
if (Float.compare(that.resolution, resolution) != 0) {
return false;
}
if (Float.compare(that.accessibilityFontScale, accessibilityFontScale) != 0) {
return false;
}
if (batteryLevel != that.batteryLevel) {
return false;
}
if (pluggedIn != that.pluggedIn) {
return false;
}
if (wifi != that.wifi) {
return false;
}
if (!operatingSystem.equals(that.operatingSystem)) {
return false;
}
if (model != null ? !model.equals(that.model) : that.model != null) {
return false;
}
if (userId != null ? !userId.equals(that.userId) : that.userId != null) {
return false;
}
if (carrier != null ? !carrier.equals(that.carrier) : that.carrier != null) {
return false;
}
if (cellularNetworkType != null ? !cellularNetworkType.equals(that.cellularNetworkType) :
that.cellularNetworkType != null) {
return false;
}
return orientation != null ? orientation.equals(that.orientation) : that.orientation == null;
}
@Override
public int hashCode() {
int result = operatingSystem != null ? operatingSystem.hashCode() : 0;
result = 31 * result + sdkIdentifier.hashCode();
result = 31 * result + sdkVersion.hashCode();
result = 31 * result + (model != null ? model.hashCode() : 0);
result = 31 * result + (userId != null ? userId.hashCode() : 0);
result = 31 * result + (carrier != null ? carrier.hashCode() : 0);
result = 31 * result + (cellularNetworkType != null ? cellularNetworkType.hashCode() : 0);
result = 31 * result + (orientation != null ? orientation.hashCode() : 0);
result = 31 * result + (resolution != +0.0f ? Float.floatToIntBits(resolution) : 0);
result = 31 * result + (accessibilityFontScale != +0.0f ? Float.floatToIntBits(accessibilityFontScale) : 0);
result = 31 * result + batteryLevel;
result = 31 * result + (pluggedIn ? 1 : 0);
result = 31 * result + (wifi ? 1 : 0);
return result;
}
@Override
public String toString() {
return "MapLoadEvent{"
+ ", operatingSystem='" + operatingSystem + '\''
+ ", sdkIdentifier='" + sdkIdentifier + '\''
+ ", sdkVersion='" + sdkVersion + '\''
+ ", model='" + model + '\''
+ ", userId='" + userId + '\''
+ ", carrier='" + carrier + '\''
+ ", cellularNetworkType='" + cellularNetworkType + '\''
+ ", orientation='" + orientation + '\''
+ ", resolution=" + resolution
+ ", accessibilityFontScale=" + accessibilityFontScale
+ ", batteryLevel=" + batteryLevel
+ ", pluggedIn=" + pluggedIn
+ ", wifi=" + wifi
+ '}';
}
}

View File

@@ -1,140 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.annotation.SuppressLint;
import androidx.annotation.FloatRange;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
/**
* Event will be sent while offline download end.
*/
@SuppressLint("ParcelCreator")
public class OfflineDownloadEndEvent extends MapBaseEvent {
private static final String EVENT_NAME = "map.offlineDownload.end";
private final Double minZoom;
private final Double maxZoom;
private final String shapeForOfflineRegion;
private String styleURL;
private String state;
private long sizeOfResourcesCompleted;
private long numberOfTilesCompleted;
OfflineDownloadEndEvent(PhoneState phoneState, String shapeForOfflineRegion,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double minZoom,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double maxZoom) {
super(phoneState);
this.shapeForOfflineRegion = shapeForOfflineRegion;
this.minZoom = minZoom;
this.maxZoom = maxZoom;
}
@Override
String getEventName() {
return EVENT_NAME;
}
Double getMinZoom() {
return minZoom;
}
Double getMaxZoom() {
return maxZoom;
}
String getShapeForOfflineRegion() {
return shapeForOfflineRegion;
}
String getStyleURL() {
return styleURL;
}
void setStyleURL(String styleURL) {
this.styleURL = styleURL;
}
long getSizeOfResourcesCompleted() {
return sizeOfResourcesCompleted;
}
void setSizeOfResourcesCompleted(long sizeOfResourcesCompleted) {
this.sizeOfResourcesCompleted = sizeOfResourcesCompleted;
}
long getNumberOfTilesCompleted() {
return numberOfTilesCompleted;
}
void setNumberOfTilesCompleted(long numberOfTilesCompleted) {
this.numberOfTilesCompleted = numberOfTilesCompleted;
}
String getState() {
return state;
}
void setState(@OfflineRegion.DownloadState int state) {
this.state = String.valueOf(state);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OfflineDownloadEndEvent that = (OfflineDownloadEndEvent) o;
if (sizeOfResourcesCompleted != that.sizeOfResourcesCompleted) {
return false;
}
if (numberOfTilesCompleted != that.numberOfTilesCompleted) {
return false;
}
if (minZoom != null ? !minZoom.equals(that.minZoom) : that.minZoom != null) {
return false;
}
if (maxZoom != null ? !maxZoom.equals(that.maxZoom) : that.maxZoom != null) {
return false;
}
if (shapeForOfflineRegion != null ? !shapeForOfflineRegion.equals(that.shapeForOfflineRegion)
: that.shapeForOfflineRegion != null) {
return false;
}
if (styleURL != null ? !styleURL.equals(that.styleURL) : that.styleURL != null) {
return false;
}
return state != null ? state.equals(that.state) : that.state == null;
}
@Override
public int hashCode() {
int result = minZoom != null ? minZoom.hashCode() : 0;
result = 31 * result + (maxZoom != null ? maxZoom.hashCode() : 0);
result = 31 * result + (shapeForOfflineRegion != null ? shapeForOfflineRegion.hashCode() : 0);
result = 31 * result + (styleURL != null ? styleURL.hashCode() : 0);
result = 31 * result + (state != null ? state.hashCode() : 0);
result = 31 * result + (int) (sizeOfResourcesCompleted ^ (sizeOfResourcesCompleted >>> 32));
result = 31 * result + (int) (numberOfTilesCompleted ^ (numberOfTilesCompleted >>> 32));
return result;
}
@Override
public String toString() {
return "OfflineDownloadEndEvent{"
+ ", minZoom=" + minZoom
+ ", maxZoom=" + maxZoom
+ ", shapeForOfflineRegion='" + shapeForOfflineRegion + '\''
+ ", styleURL='" + styleURL + '\''
+ ", sizeOfResourcesCompleted=" + sizeOfResourcesCompleted
+ ", numberOfTilesCompleted=" + numberOfTilesCompleted
+ ", state=" + state
+ '}';
}
}

View File

@@ -1,98 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.annotation.SuppressLint;
import androidx.annotation.FloatRange;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
/**
* Event will be sent while offline download start.
*/
@SuppressLint("ParcelCreator")
public class OfflineDownloadStartEvent extends MapBaseEvent {
private static final String EVENT_NAME = "map.offlineDownload.start";
private final Double minZoom;
private final Double maxZoom;
private final String shapeForOfflineRegion;
private String styleURL;
OfflineDownloadStartEvent(PhoneState phoneState, String shapeForOfflineRegion,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double minZoom,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) Double maxZoom) {
super(phoneState);
this.shapeForOfflineRegion = shapeForOfflineRegion;
this.minZoom = minZoom;
this.maxZoom = maxZoom;
}
@Override
String getEventName() {
return EVENT_NAME;
}
Double getMinZoom() {
return minZoom;
}
Double getMaxZoom() {
return maxZoom;
}
String getShapeForOfflineRegion() {
return shapeForOfflineRegion;
}
String getStyleURL() {
return styleURL;
}
void setStyleURL(String styleURL) {
this.styleURL = styleURL;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OfflineDownloadStartEvent that = (OfflineDownloadStartEvent) o;
if (minZoom != null ? !minZoom.equals(that.minZoom) : that.minZoom != null) {
return false;
}
if (maxZoom != null ? !maxZoom.equals(that.maxZoom) : that.maxZoom != null) {
return false;
}
if (shapeForOfflineRegion != null ? !shapeForOfflineRegion.equals(that.shapeForOfflineRegion) :
that.shapeForOfflineRegion != null) {
return false;
}
return styleURL != null ? styleURL.equals(that.styleURL) : that.styleURL == null;
}
@Override
public int hashCode() {
int result = minZoom != null ? minZoom.hashCode() : 0;
result = 31 * result + (maxZoom != null ? maxZoom.hashCode() : 0);
result = 31 * result + (shapeForOfflineRegion != null ? shapeForOfflineRegion.hashCode() : 0);
result = 31 * result + (styleURL != null ? styleURL.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "OfflineDownloadStartEvent{"
+ ", minZoom=" + minZoom
+ ", maxZoom=" + maxZoom
+ ", shapeForOfflineRegion='" + shapeForOfflineRegion + '\''
+ ", styleURL='" + styleURL + '\''
+ '}';
}
}

View File

@@ -1,179 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.annotation.SuppressLint;
import android.os.Bundle;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.util.ArrayList;
import java.util.List;
/**
* Generic Performance Event that can be used for performance measurements.
* Customer measurements can be added to the bundle.
* <p>
* Bundle is expected to have following properties:
* "attributes", "counters", and "metadata" with String values.
* </p>
* <p>
* Attributes: a string representing an array of name/string value pair objects.
* Counters: a string representing an array of name/number value pair objects.
* Metadata is a string representation of a JsonObject with string values.
* </p>
* <p>
* Here is an example of a Performance event bundle data:
* </p>
* <p>
* "attributes": [{ "name": "style_id", "value": "mapbox://styles/mapbox/streets-v10"}]
* </p>
* <p>
* "counters": [{"name": "fps_average", "value": 90.7655486547093},
* {"name": "fps_deviation", "value": 29.301809631465574}]
* “metadata”: {
* “version”: “9”,
* “screenSize”: “1080x1794”,
* “country”: “US”,
* “device”: “Pixel 2”,
* “abi”: “arm64-v8a”,
* “brand”: “google”,
* “ram”: “3834167296”,
* “os”: “android”,
* “gpu”: “Qualcomm, Adreno (TM) 540, OpenGL ES 3.2 V@313.0 (GIT@7bf2852, Ie32bfa6f6f)“,
* “manufacturer”: “Google”
* }
* </p>
*/
@SuppressLint("ParcelCreator")
class PerformanceEvent extends MapBaseEvent {
private static final String PERFORMANCE_TRACE = "mobile.performance_trace";
private final String sessionId;
private final List<PerformanceAttribute<String>> attributes;
private final List<PerformanceAttribute<Double>> counters;
private final JsonObject metadata;
PerformanceEvent(PhoneState phoneState, String sessionId, Bundle bundle) {
super(phoneState);
this.sessionId = sessionId;
this.attributes = initList(bundle.getString("attributes"),
new TypeToken<ArrayList<PerformanceAttribute<String>>>() {
});
this.counters = initList(bundle.getString("counters"),
new TypeToken<ArrayList<PerformanceAttribute<Double>>>() {
});
this.metadata = initMetaData(bundle.getString("metadata"));
}
private <T> ArrayList<PerformanceAttribute<T>> initList(String fromString, TypeToken typeToken) {
if (fromString == null || fromString.isEmpty()) {
return new ArrayList<>();
}
return new Gson().fromJson(fromString, typeToken.getType());
}
private JsonObject initMetaData(String fromString) {
if (fromString == null) {
return new JsonObject();
} else {
return new Gson().fromJson(fromString, JsonObject.class);
}
}
@Override
String getEventName() {
return PERFORMANCE_TRACE;
}
String getSessionId() {
return sessionId;
}
List<PerformanceAttribute<String>> getAttributes() {
return attributes;
}
List<PerformanceAttribute<Double>> getCounters() {
return counters;
}
JsonObject getMetadata() {
return metadata;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PerformanceEvent that = (PerformanceEvent) o;
if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) {
return false;
}
if (attributes != null ? !attributes.equals(that.attributes) : that.attributes != null) {
return false;
}
if (counters != null ? !counters.equals(that.counters) : that.counters != null) {
return false;
}
return metadata != null ? metadata.equals(that.metadata) : that.metadata == null;
}
@Override
public int hashCode() {
int result = sessionId != null ? sessionId.hashCode() : 0;
result = 31 * result + (attributes != null ? attributes.hashCode() : 0);
result = 31 * result + (counters != null ? counters.hashCode() : 0);
result = 31 * result + (metadata != null ? metadata.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "PerformanceEvent{"
+ "sessionId='" + sessionId + '\''
+ ", attributes=" + attributes
+ ", counters=" + counters
+ ", metadata=" + metadata
+ '}';
}
static class PerformanceAttribute<T> {
private final String name;
private final T value;
PerformanceAttribute(String name, T value) {
this.name = name;
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PerformanceAttribute<?> that = (PerformanceAttribute<?>) o;
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
return value != null ? value.equals(that.value) : that.value == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
}
}

View File

@@ -1,171 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.content.Context;
import android.content.res.Configuration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import androidx.annotation.NonNull;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.mapbox.android.telemetry.TelemetryUtils;
/**
* Class that holds kinds of states of the current phone.
*/
class PhoneState {
private static final String NO_CARRIER = "EMPTY_CARRIER";
private static final int NO_NETWORK = -1;
private String created;
private String cellularNetworkType;
private Orientation orientation;
private String carrier;
private int batteryLevel;
private boolean pluggedIn;
private boolean wifi;
private float accessibilityFontScale;
private float resolution;
PhoneState() {
}
PhoneState(@NonNull Context context) {
this.created = TelemetryUtils.obtainCurrentDate();
this.batteryLevel = TelemetryUtils.obtainBatteryLevel(context);
this.pluggedIn = TelemetryUtils.isPluggedIn(context);
this.cellularNetworkType = TelemetryUtils.obtainCellularNetworkType(context);
this.orientation = Orientation.getOrientation(context.getResources().getConfiguration().orientation);
this.accessibilityFontScale = context.getResources().getConfiguration().fontScale;
this.carrier = obtainCellularCarrier(context);
this.resolution = obtainDisplayDensity(context);
this.wifi = isConnectedToWifi(context);
}
private String obtainCellularCarrier(@NonNull Context context) {
TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (manager == null) {
return NO_CARRIER;
}
String carrierName = manager.getNetworkOperatorName();
if (TextUtils.isEmpty(carrierName)) {
return NO_CARRIER;
}
return carrierName;
}
private float obtainDisplayDensity(@NonNull Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.density;
}
private boolean isConnectedToWifi(@NonNull Context context) {
try {
WifiManager wifiMgr = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiMgr == null) {
return false;
}
//noinspection MissingPermission
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
return wifiMgr.isWifiEnabled() && wifiInfo.getNetworkId() != NO_NETWORK;
} catch (Exception exception) {
return false;
}
}
String getCreated() {
return created;
}
void setCreated(String created) {
this.created = created;
}
int getBatteryLevel() {
return batteryLevel;
}
void setBatteryLevel(int batteryLevel) {
this.batteryLevel = batteryLevel;
}
boolean isPluggedIn() {
return pluggedIn;
}
void setPluggedIn(boolean pluggedIn) {
this.pluggedIn = pluggedIn;
}
String getCellularNetworkType() {
return cellularNetworkType;
}
void setCellularNetworkType(String cellularNetworkType) {
this.cellularNetworkType = cellularNetworkType;
}
String getOrientation() {
return orientation.getOrientation();
}
void setOrientation(Orientation orientation) {
this.orientation = orientation;
}
String getCarrier() {
return carrier;
}
void setCarrier(String carrier) {
this.carrier = carrier;
}
boolean isWifi() {
return wifi;
}
void setWifi(boolean wifi) {
this.wifi = wifi;
}
float getAccessibilityFontScale() {
return accessibilityFontScale;
}
void setAccessibilityFontScale(float accessibilityFontScale) {
this.accessibilityFontScale = accessibilityFontScale;
}
float getResolution() {
return resolution;
}
void setResolution(float resolution) {
this.resolution = resolution;
}
enum Orientation {
ORIENTATION_PORTRAIT("Portrait"),
ORIENTATION_LANDSCAPE("Landscape");
private String orientation;
Orientation(String orientation) {
this.orientation = orientation;
}
public static Orientation getOrientation(int index) {
if (Configuration.ORIENTATION_PORTRAIT == index) {
return ORIENTATION_PORTRAIT;
}
return ORIENTATION_LANDSCAPE;
}
public String getOrientation() {
return orientation;
}
}
}

View File

@@ -1,136 +0,0 @@
package com.mapbox.mapboxsdk.module.telemetry;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
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;
import com.mapbox.android.telemetry.TelemetryEnabler;
import com.mapbox.mapboxsdk.BuildConfig;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;
import java.util.UUID;
public class TelemetryImpl implements TelemetryDefinition {
private final MapboxTelemetry telemetry;
private final Context appContext;
public TelemetryImpl() {
appContext = Mapbox.getApplicationContext();
String accessToken = Mapbox.getAccessToken();
telemetry = new MapboxTelemetry(appContext, accessToken, BuildConfig.MAPBOX_EVENTS_USER_AGENT);
TelemetryEnabler.State telemetryState = TelemetryEnabler.retrieveTelemetryStateFromPreferences();
if (TelemetryEnabler.State.ENABLED.equals(telemetryState)) {
telemetry.enable();
}
}
/**
* Register the app user turnstile event
*/
@Override
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)));
}
/**
* Register an end-user gesture interaction event.
*
* @param eventType type of gesture event occurred
* @param latitude the latitude value of the gesture focal point
* @param longitude the longitude value of the gesture focal point
* @param zoom current zoom of the map
* @deprecated since 7.5.0, this event is no longer supported
*/
@Deprecated
@Override
public void onGestureInteraction(String eventType, double latitude, double longitude,
@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) double zoom) {
//no-op
}
/**
* Set the end-user selected state to participate or opt-out in telemetry collection.
*
* @param enabledTelemetry true if enabled, false otherwise
*/
@Override
public void setUserTelemetryRequestState(boolean enabledTelemetry) {
if (enabledTelemetry) {
TelemetryEnabler.updateTelemetryState(TelemetryEnabler.State.ENABLED);
telemetry.enable();
} else {
telemetry.disable();
TelemetryEnabler.updateTelemetryState(TelemetryEnabler.State.DISABLED);
}
}
@Override
public void disableTelemetrySession() {
telemetry.disable();
}
/**
* Set the debug logging state of telemetry.
*
* @param debugLoggingEnabled true to enable logging
*/
@Override
public void setDebugLoggingEnabled(boolean debugLoggingEnabled) {
telemetry.updateDebugLoggingEnabled(debugLoggingEnabled);
}
/**
* Set the telemetry rotation session id interval
*
* @param interval the selected session interval
* @return true if rotation session id was updated
*/
@Override
public boolean setSessionIdRotationInterval(int interval) {
return telemetry.updateSessionIdRotationInterval(new SessionInterval(interval));
}
/**
* Register an offline region creation event.
*
* @param offlineDefinition the offline region definition
*/
@Override
public void onCreateOfflineRegion(@NonNull OfflineRegionDefinition offlineDefinition) {
telemetry.push(MapEventFactory.buildOfflineDownloadStartEvent(new PhoneState(appContext),
offlineDefinition instanceof OfflineTilePyramidRegionDefinition ? "tileregion" : "shaperegion",
offlineDefinition.getMinZoom(),
offlineDefinition.getMaxZoom(),
offlineDefinition.getStyleURL())
);
}
/**
* Register a performance event
*
* @param data performance event data
*/
@Override
public void onPerformanceEvent(Bundle data) {
if (data == null) {
data = new Bundle();
}
telemetry.push(MapEventFactory.buildPerformanceEvent(new PhoneState(appContext),
UUID.randomUUID().toString(), data));
}
}

View File

@@ -1,4 +0,0 @@
/**
* Contains the Mapbox Maps Android Modular Telemetry API classes.
*/
package com.mapbox.mapboxsdk.module.telemetry;

View File

@@ -13,7 +13,6 @@ import com.mapbox.mapboxsdk.LibraryLoader;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
import com.mapbox.mapboxsdk.utils.FileUtils;
@@ -642,12 +641,6 @@ public class OfflineManager {
});
}
});
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
LatLngBounds bounds = definition.getBounds();
telemetry.onCreateOfflineRegion(definition);
}
}
/**

View File

@@ -63,7 +63,7 @@ public interface OfflineRegionDefinition extends Parcelable {
boolean getIncludeIdeographs();
/**
* Gets the type of the OfflineRegionDefinition for telemetry ("tileregion", "shaperegion").
* Gets the type of the OfflineRegionDefinition ("tileregion", "shaperegion").
*
* @return The type of the OfflineRegionDefinition.
*/

View File

@@ -25,7 +25,6 @@ import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.Image;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.storage.FileSource;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.sources.Source;
@@ -391,10 +390,7 @@ public class MapSnapshotter {
checkThread();
this.context = context.getApplicationContext();
this.options = options;
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
telemetry.onAppUserTurnstileEvent();
}
FileSource fileSource = FileSource.getInstance(context);
String apiBaseUrl = options.getApiBaseUrl();
if (!TextUtils.isEmpty(apiBaseUrl)) {