[android] Added geofences in/out logic

This commit is contained in:
Dmitry Donskoy 2018-12-20 16:45:33 +03:00 committed by Aleksandr Zatsepin
parent 39819f0295
commit e6ded00a58
6 changed files with 289 additions and 82 deletions

View file

@ -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);
}
}
}

View file

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

View file

@ -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);
}
}
}
}

View file

@ -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);
}

View file

@ -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<GeoFenceFeature> 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<String> 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();
}

View file

@ -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<Geofence> 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<Object>
{
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<Geofence> mGeofences;
GeofencingEventTask(@NonNull Application application, @NonNull GeofencingEvent geofencingEvent)
CheckLocationTask(@NonNull Application application, @NonNull List<Geofence> 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);
}
}
}