From e6ded00a5877ce9835bb8c1c0ac6f8aa4602b08c Mon Sep 17 00:00:00 2001 From: Dmitry Donskoy Date: Thu, 20 Dec 2018 16:45:33 +0300 Subject: [PATCH] [android] Added geofences in/out logic --- .../maps/AppBaseTransitionListener.java | 56 +++++ .../com/mapswithme/maps/LightFramework.java | 16 +- .../com/mapswithme/maps/MwmApplication.java | 54 +++-- .../maps/geofence/GeofenceRegistry.java | 4 + .../maps/geofence/GeofenceRegistryImpl.java | 47 +++-- .../GeofenceTransitionsIntentService.java | 194 +++++++++++++++--- 6 files changed, 289 insertions(+), 82 deletions(-) create mode 100644 android/src/com/mapswithme/maps/AppBaseTransitionListener.java diff --git a/android/src/com/mapswithme/maps/AppBaseTransitionListener.java b/android/src/com/mapswithme/maps/AppBaseTransitionListener.java new file mode 100644 index 0000000000..223e4291d7 --- /dev/null +++ b/android/src/com/mapswithme/maps/AppBaseTransitionListener.java @@ -0,0 +1,56 @@ +package com.mapswithme.maps; + +import android.location.Location; +import android.support.annotation.NonNull; +import android.util.Log; + +import com.mapswithme.maps.background.AppBackgroundTracker; +import com.mapswithme.maps.geofence.GeofenceLocation; +import com.mapswithme.maps.geofence.GeofenceRegistry; +import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.location.LocationPermissionNotGrantedException; +import com.mapswithme.util.log.LoggerFactory; + +class AppBaseTransitionListener implements AppBackgroundTracker.OnTransitionListener +{ + @NonNull + private final MwmApplication mApplication; + + AppBaseTransitionListener(@NonNull MwmApplication application) + { + mApplication = application; + } + + @Override + public void onTransit(boolean foreground) + { + if (!foreground && LoggerFactory.INSTANCE.isFileLoggingEnabled()) + { + Log.i(MwmApplication.TAG, "The app goes to background. All logs are going to be zipped."); + LoggerFactory.INSTANCE.zipLogs(null); + } + + if (foreground) + return; + + updateGeofences(); + } + + private void updateGeofences() + { + Location lastKnownLocation = LocationHelper.INSTANCE.getLastKnownLocation(); + if (lastKnownLocation == null) + return; + + GeofenceRegistry geofenceRegistry = mApplication.getGeofenceRegistry(); + try + { + geofenceRegistry.unregisterGeofences(); + geofenceRegistry.registerGeofences(GeofenceLocation.from(lastKnownLocation)); + } + catch (LocationPermissionNotGrantedException e) + { + mApplication.getLogger().d(MwmApplication.TAG, "Location permission not granted!", e); + } + } +} diff --git a/android/src/com/mapswithme/maps/LightFramework.java b/android/src/com/mapswithme/maps/LightFramework.java index 9bcaa8636d..d40093b820 100644 --- a/android/src/com/mapswithme/maps/LightFramework.java +++ b/android/src/com/mapswithme/maps/LightFramework.java @@ -5,6 +5,7 @@ import android.support.annotation.Nullable; import com.mapswithme.maps.background.NotificationCandidate; import com.mapswithme.maps.geofence.GeoFenceFeature; +import com.mapswithme.maps.geofence.GeofenceLocation; import java.util.Arrays; import java.util.Collections; @@ -29,9 +30,18 @@ public class LightFramework maxCount))); } - public static native void nativeLogLocalAdsEvent(int type, double lat, double lon, - int accuracyInMeters, long mwmVersion, - @NonNull String countryId, int featureIndex); + public static void logLocalAdsEvent(@NonNull GeofenceLocation location, + @NonNull GeoFenceFeature feature) + { + nativeLogLocalAdsEvent(1, location.getLat(), location.getLon(), + /* FIXME */ + (int) location.getRadiusInMeters(), feature.getMwmVersion(), + feature.getCountryId(), feature.getFeatureIndex()); + } + + private static native void nativeLogLocalAdsEvent(int type, double lat, double lon, + int accuracyInMeters, long mwmVersion, + @NonNull String countryId, int featureIndex); @Nullable public static native NotificationCandidate nativeGetNotification(); } diff --git a/android/src/com/mapswithme/maps/MwmApplication.java b/android/src/com/mapswithme/maps/MwmApplication.java index 78f82a08dc..970a063414 100644 --- a/android/src/com/mapswithme/maps/MwmApplication.java +++ b/android/src/com/mapswithme/maps/MwmApplication.java @@ -7,7 +7,6 @@ import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.multidex.MultiDex; -import android.util.Log; import com.appsflyer.AppsFlyerLib; import com.mapswithme.maps.analytics.ExternalLibrariesMediator; @@ -44,11 +43,15 @@ import com.mapswithme.util.statistics.Statistics; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class MwmApplication extends Application { + @SuppressWarnings("NullableProblems") + @NonNull private Logger mLogger; - private final static String TAG = "MwmApplication"; + public final static String TAG = "MwmApplication"; private static MwmApplication sSelf; private SharedPreferences mPrefs; @@ -84,6 +87,9 @@ public class MwmApplication extends Application @SuppressWarnings("NullableProblems") @NonNull private GeofenceRegistry mGeofenceRegistry; + @SuppressWarnings("NullableProblems") + @NonNull + private ExecutorService mGeofenceExecutor; @NonNull public SubwayManager getSubwayManager() @@ -151,10 +157,10 @@ public class MwmApplication extends Application public void onCreate() { super.onCreate(); - mBackgroundListener = new TransitionListener(this); + mBackgroundListener = new AppBaseTransitionListener(this); LoggerFactory.INSTANCE.initialize(this); mLogger = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); - mLogger.d(TAG, "Application is created"); + getLogger().d(TAG, "Application is created"); mMainLoopHandler = new Handler(getMainLooper()); mMediator = new ExternalLibrariesMediator(this); mMediator.initSensitiveDataToleranceLibraries(); @@ -173,6 +179,7 @@ public class MwmApplication extends Application mPurchaseOperationObservable = new PurchaseOperationObservable(); mPlayer = new MediaPlayerWrapper(this); mGeofenceRegistry = new GeofenceRegistryImpl(this); + mGeofenceExecutor = Executors.newSingleThreadExecutor(); } private void initNotificationChannels() @@ -206,11 +213,11 @@ public class MwmApplication extends Application final boolean isInstallationIdFound = mMediator.setInstallationIdToCrashlytics(); final String settingsPath = StorageUtils.getSettingsPath(); - mLogger.d(TAG, "onCreate(), setting path = " + settingsPath); + getLogger().d(TAG, "onCreate(), setting path = " + settingsPath); final String filesPath = StorageUtils.getFilesPath(this); - mLogger.d(TAG, "onCreate(), files path = " + filesPath); + getLogger().d(TAG, "onCreate(), files path = " + filesPath); final String tempPath = StorageUtils.getTempPath(this); - mLogger.d(TAG, "onCreate(), temp path = " + tempPath); + getLogger().d(TAG, "onCreate(), temp path = " + tempPath); // If platform directories are not created it means that native part of app will not be able // to work at all. So, we just ignore native part initialization in this case, e.g. when the @@ -361,6 +368,12 @@ public class MwmApplication extends Application return mGeofenceRegistry; } + @NonNull + public ExecutorService getGeofenceProbesExecutor() + { + return mGeofenceExecutor; + } + private native void nativeInitPlatform(String apkPath, String storagePath, String privatePath, String tmpPath, String obbGooglePath, String flavorName, String buildType, boolean isTablet); @@ -368,6 +381,12 @@ public class MwmApplication extends Application private static native void nativeProcessTask(long taskPointer); private static native void nativeAddLocalization(String name, String value); + @NonNull + public Logger getLogger() + { + return mLogger; + } + private static class VisibleAppLaunchListener implements AppBackgroundTracker.OnVisibleAppLaunchListener { @Override @@ -399,25 +418,4 @@ public class MwmApplication extends Application @Override public void onProgress(String countryId, long localSize, long remoteSize) {} } - - private static class TransitionListener implements AppBackgroundTracker.OnTransitionListener - { - @NonNull - private final MwmApplication mApplication; - - TransitionListener(@NonNull MwmApplication application) - { - mApplication = application; - } - - @Override - public void onTransit(boolean foreground) - { - if (!foreground && LoggerFactory.INSTANCE.isFileLoggingEnabled()) - { - Log.i(TAG, "The app goes to background. All logs are going to be zipped."); - LoggerFactory.INSTANCE.zipLogs(null); - } - } - } } diff --git a/android/src/com/mapswithme/maps/geofence/GeofenceRegistry.java b/android/src/com/mapswithme/maps/geofence/GeofenceRegistry.java index 1e0f1f3f37..6559864ece 100644 --- a/android/src/com/mapswithme/maps/geofence/GeofenceRegistry.java +++ b/android/src/com/mapswithme/maps/geofence/GeofenceRegistry.java @@ -2,10 +2,14 @@ package com.mapswithme.maps.geofence; import android.support.annotation.NonNull; +import com.google.android.gms.location.Geofence; import com.mapswithme.maps.location.LocationPermissionNotGrantedException; public interface GeofenceRegistry { void registerGeofences(@NonNull GeofenceLocation location) throws LocationPermissionNotGrantedException; void unregisterGeofences() throws LocationPermissionNotGrantedException; + + @NonNull + GeoFenceFeature getFeatureByGeofence(@NonNull Geofence geofence); } diff --git a/android/src/com/mapswithme/maps/geofence/GeofenceRegistryImpl.java b/android/src/com/mapswithme/maps/geofence/GeofenceRegistryImpl.java index b573dee7a4..eeae7d0d2d 100644 --- a/android/src/com/mapswithme/maps/geofence/GeofenceRegistryImpl.java +++ b/android/src/com/mapswithme/maps/geofence/GeofenceRegistryImpl.java @@ -14,16 +14,22 @@ import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.location.LocationPermissionNotGrantedException; import com.mapswithme.util.PermissionsUtils; import com.mapswithme.util.concurrency.UiThread; +import com.mapswithme.util.log.Logger; +import com.mapswithme.util.log.LoggerFactory; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; +import java.util.concurrent.TimeUnit; public class GeofenceRegistryImpl implements GeofenceRegistry { private static final int GEOFENCE_MAX_COUNT = 100; + private static final int GEOFENCE_TTL_IN_DAYS = 3; private static final float PREFERRED_GEOFENCE_RADIUS = 100.0f; + private static final Logger LOG = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); + private static final String TAG = GeofenceRegistryImpl.class.getSimpleName(); + @NonNull private final Application mApplication; @NonNull @@ -45,7 +51,7 @@ public class GeofenceRegistryImpl implements GeofenceRegistry checkPermission(); List features = LightFramework.getLocalAdsFeatures( - location.getLat(), location.getLon(), location.getRadiusInMeters()/* from system location provider accuracy */, GEOFENCE_MAX_COUNT); + location.getLat(), location.getLon(), location.getRadiusInMeters(), GEOFENCE_MAX_COUNT); if (features.isEmpty()) return; @@ -54,7 +60,7 @@ public class GeofenceRegistryImpl implements GeofenceRegistry Geofence geofence = new Geofence.Builder() .setRequestId(each.getId()) .setCircularRegion(each.getLatitude(), each.getLongitude(), PREFERRED_GEOFENCE_RADIUS) - .setExpirationDuration(Geofence.NEVER_EXPIRE) + .setExpirationDuration(TimeUnit.DAYS.toMillis(GEOFENCE_TTL_IN_DAYS)) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .build(); @@ -72,38 +78,43 @@ public class GeofenceRegistryImpl implements GeofenceRegistry { checkThread(); checkPermission(); - - if (mGeofences.isEmpty()) - return; - List expiredGeofences = new ArrayList<>(); - for (GeofenceAndFeature current: mGeofences) - { - String requestId = current.getGeofence().getRequestId(); - expiredGeofences.add(requestId); - } - mGeofencingClient.removeGeofences(expiredGeofences) + mGeofencingClient.removeGeofences(makeGeofencePendingIntent()) .addOnSuccessListener(params -> onRemoveFailed()) .addOnSuccessListener(params -> onRemoveSucceeded()); } + @NonNull + @Override + public GeoFenceFeature getFeatureByGeofence(@NonNull Geofence geofence) + { + checkThread(); + for (GeofenceAndFeature each : mGeofences) + { + if (each.getGeofence().getRequestId().equals(geofence.getRequestId())) + return each.getFeature(); + + } + throw new IllegalArgumentException("Geofence not found"); + } + private void onAddSucceeded() { - + LOG.d(TAG, "onAddSucceeded"); } private void onAddFailed() { - + LOG.d(TAG, "onAddFailed"); } private void onRemoveSucceeded() { - + LOG.d(TAG, "onRemoveSucceeded"); } private void onRemoveFailed() { - + LOG.d(TAG, "onRemoveFailed"); } private void checkPermission() throws LocationPermissionNotGrantedException @@ -129,7 +140,7 @@ public class GeofenceRegistryImpl implements GeofenceRegistry private GeofencingRequest makeGeofencingRequest() { GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); - return builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL) + return builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) .addGeofences(collectGeofences()) .build(); } diff --git a/android/src/com/mapswithme/maps/geofence/GeofenceTransitionsIntentService.java b/android/src/com/mapswithme/maps/geofence/GeofenceTransitionsIntentService.java index feb7ae9c69..80fd8b818f 100644 --- a/android/src/com/mapswithme/maps/geofence/GeofenceTransitionsIntentService.java +++ b/android/src/com/mapswithme/maps/geofence/GeofenceTransitionsIntentService.java @@ -11,17 +11,29 @@ import android.support.v4.app.JobIntentService; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingEvent; +import com.mapswithme.maps.LightFramework; +import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.location.LocationHelper; import com.mapswithme.maps.location.LocationPermissionNotGrantedException; import com.mapswithme.maps.scheduling.JobIdMap; import com.mapswithme.util.log.Logger; import com.mapswithme.util.log.LoggerFactory; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + public class GeofenceTransitionsIntentService extends JobIntentService { private static final Logger LOG = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); - private static final String TAG = GeofenceTransitionsIntentService.class.getSimpleName(); + public static final int LOCATION_PROBES_MAX_COUNT = 10; + public static final int TIMEOUT_IN_MINUTS = 1; @NonNull private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); @@ -31,61 +43,177 @@ public class GeofenceTransitionsIntentService extends JobIntentService { GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent.hasError()) - { - String errorMessage = "Error code = " + geofencingEvent.getErrorCode(); - LOG.e(TAG, errorMessage); - return; - } + onError(geofencingEvent); + else + onSuccess(geofencingEvent); + } + + private void onSuccess(@NonNull GeofencingEvent geofencingEvent) + { int transitionType = geofencingEvent.getGeofenceTransition(); - if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER - || transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) + + if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) + onGeofenceEnter(geofencingEvent); + else if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) + onGeofenceExit(geofencingEvent); + } + + private void onGeofenceExit(@NonNull GeofencingEvent geofencingEvent) + { + GeofenceLocation geofenceLocation = GeofenceLocation.from(geofencingEvent.getTriggeringLocation()); + mMainThreadHandler.post(new GeofencingEventExitTask(getApplication(), geofenceLocation)); + } + + private void onGeofenceEnter(@NonNull GeofencingEvent geofencingEvent) + { + makeLocationProbesBlocking(geofencingEvent); + } + + private void makeLocationProbesBlocking(@NonNull GeofencingEvent geofencingEvent) + { + for (int i = 0; i < LOCATION_PROBES_MAX_COUNT; i++) { - mMainThreadHandler.post(new GeofencingEventTask(getApplication(), geofencingEvent)); -// LightFramework.nativeLogLocalAdsEvent(1, /* myPlaceLat */, /* myPlaceLon */, /* -// locationProviderAccuracy */, , , ); + try + { + makeSingleLocationProbOrThrow(geofencingEvent); + } + catch (InterruptedException| ExecutionException | TimeoutException e) + { + LOG.d(TAG, "error", e); + } } } + @NonNull + private ExecutorService getExecutor() + { + MwmApplication app = (MwmApplication) getApplication(); + return app.getGeofenceProbesExecutor(); + } + + private void makeSingleLocationProbOrThrow(GeofencingEvent geofencingEvent) throws + InterruptedException, ExecutionException, TimeoutException + { + getExecutor().submit(new InfinityTask()).get(TIMEOUT_IN_MINUTS, TimeUnit.MINUTES); + GeofenceLocation geofenceLocation = GeofenceLocation.from(geofencingEvent.getTriggeringLocation()); + List geofences = Collections.unmodifiableList(geofencingEvent.getTriggeringGeofences()); + CheckLocationTask locationTask = new CheckLocationTask( + getApplication(), + geofences, + geofenceLocation); + mMainThreadHandler.post(locationTask); + } + + private void onError(@NonNull GeofencingEvent geofencingEvent) + { + String errorMessage = "Error code = " + geofencingEvent.getErrorCode(); + LOG.e(TAG, errorMessage); + } + public static void enqueueWork(@NonNull Context context, @NonNull Intent intent) { int id = JobIdMap.getId(GeofenceTransitionsIntentService.class); enqueueWork(context, GeofenceTransitionsIntentService.class, id, intent); } - private static class GeofencingEventTask implements Runnable + private static class InfinityTask implements Callable + { + private static final int LATCH_COUNT = 1; + + @Override + public Object call() throws Exception + { + CountDownLatch latch = new CountDownLatch(LATCH_COUNT); + latch.await(); + return null; + } + } + + private static class CheckLocationTask extends AbstractGeofenceTask { @NonNull - private final Application mApplication; - @NonNull - private final GeofencingEvent mGeofencingEvent; + private final List mGeofences; - GeofencingEventTask(@NonNull Application application, @NonNull GeofencingEvent geofencingEvent) + CheckLocationTask(@NonNull Application application, @NonNull List geofences, + @NonNull GeofenceLocation triggeringLocation) { - mApplication = application; - mGeofencingEvent = geofencingEvent; + super(application, triggeringLocation); + mGeofences = geofences; } @Override public void run() { - Location lastKnownLocation = LocationHelper.INSTANCE.getLastKnownLocation(); - Location currentLocation = lastKnownLocation == null ? mGeofencingEvent - .getTriggeringLocation() - : lastKnownLocation; + requestLocationCheck(); + } - GeofenceRegistry geofenceRegistry = GeofenceRegistryImpl.from(mApplication); - if (mGeofencingEvent.getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_EXIT) + private void requestLocationCheck() + { + GeofenceLocation geofenceLocation = getGeofenceLocation(); + if (!getApplication().arePlatformAndCoreInitialized()) + getApplication().initCore(); + + GeofenceRegistry registry = GeofenceRegistryImpl.from(getApplication()); + for (Geofence each : mGeofences) { - try - { - geofenceRegistry.unregisterGeofences(); - geofenceRegistry.registerGeofences(GeofenceLocation.from(currentLocation)); - } - catch (LocationPermissionNotGrantedException e) - { - LOG.d(TAG, "Location permission not granted!", e); - } + GeoFenceFeature geoFenceFeature = registry.getFeatureByGeofence(each); + LightFramework.logLocalAdsEvent(geofenceLocation, geoFenceFeature); } } } + + private class GeofencingEventExitTask extends AbstractGeofenceTask + { + GeofencingEventExitTask(@NonNull Application application, + @NonNull GeofenceLocation location) + { + super(application, location); + } + + @Override + public void run() + { + GeofenceLocation location = getGeofenceLocation(); + GeofenceRegistry geofenceRegistry = GeofenceRegistryImpl.from(getApplication()); + + try + { + geofenceRegistry.unregisterGeofences(); + geofenceRegistry.registerGeofences(location); + } + catch (LocationPermissionNotGrantedException e) + { + LOG.d(TAG, "Location permission not granted!", e); + } + } + } + + private static abstract class AbstractGeofenceTask implements Runnable + { + @NonNull + private final MwmApplication mApplication; + @NonNull + private final GeofenceLocation mGeofenceLocation; + + AbstractGeofenceTask(@NonNull Application application, + @NonNull GeofenceLocation location) + { + mApplication = (MwmApplication)application; + mGeofenceLocation = location; + } + + @NonNull + protected MwmApplication getApplication() + { + return mApplication; + } + + @NonNull + protected GeofenceLocation getGeofenceLocation() + { + Location lastKnownLocation = LocationHelper.INSTANCE.getLastKnownLocation(); + return lastKnownLocation == null ? mGeofenceLocation + : GeofenceLocation.from(lastKnownLocation); + } + } }