[android] Added GeofenceRegistry interface

This commit is contained in:
Dmitry Donskoy 2018-12-18 15:39:56 +03:00 committed by Aleksandr Zatsepin
parent e2d1c4a5dc
commit b2c5210d8d
13 changed files with 318 additions and 284 deletions

View file

@ -568,20 +568,14 @@
android:name="com.pushwoosh.location.GeoLocationService"/>
<service android:name="com.mapswithme.util.push.GcmInstanceIDListenerService"/>
<service android:name="com.mapswithme.util.push.GcmMessageHandlerService"/>
<receiver android:name="com.mapswithme.maps.GeofenceReceiver"
android:enabled="true"
android:exported="true">
<intent-filter >
<action android:name="com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE"/>
</intent-filter>
</receiver>
<service
android:name="com.mapswithme.maps.scheduling.GeofenceTransitionsIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
<receiver android:name="com.mapswithme.maps.GeofenceReceiver"
android:enabled="true"
android:exported="true">
</receiver>
<!-- Catches app upgraded intent -->
<receiver android:name=".background.UpgradeReceiver">
<intent-filter>

View file

@ -1,162 +1,16 @@
package com.mapswithme.maps;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.gms.location.Geofence;
import com.mapswithme.maps.scheduling.GeofenceTransitionsIntentService;
import java.util.List;
public class GeofenceReceiver extends BroadcastReceiver
{
Context context;
Intent broadcastIntent = new Intent();
@Override
public void onReceive(Context context, Intent intent) {
GeofenceTransitionsIntentService.enqueueWork(context, intent);
this.context = context;
/* broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);
if (LocationClient.hasError(intent)) {
handleError(intent);
} else {
handleEnterExit(intent);
}*/
}
/*
private void handleError(Intent intent){
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Get the error message
String errorMessage = LocationServiceErrorMessages.getErrorString(
context, errorCode);
// Log the error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_error_detail,
errorMessage));
// Set the action and error message for the broadcast intent
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, errorMessage);
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(context).sendBroadcast(
broadcastIntent);
}
private void handleEnterExit(Intent intent) {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)
|| (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
// Post a notification
List<Geofence> geofences = LocationClient
.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
String ids = TextUtils.join(GeofenceUtils.GEOFENCE_ID_DELIMITER,
geofenceIds);
String transitionType = GeofenceUtils
.getTransitionString(transition);
for (int index = 0; index < geofences.size(); index++) {
Geofence geofence = geofences.get(index);
...do something with the geofence entry or exit. I'm saving them to a local sqlite db
}
// Create an Intent to broadcast to the app
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_TRANSITION)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_ID, geofenceIds)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_TRANSITION_TYPE,
transitionType);
LocalBroadcastManager.getInstance(MyApplication.getContext())
.sendBroadcast(broadcastIntent);
// Log the transition type and a message
Log.d(GeofenceUtils.APPTAG, transitionType + ": " + ids);
Log.d(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_notification_text));
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, "transition");
// An invalid transition was reported
} else {
// Always log as an error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_invalid_type,
transition));
}
}
*/
/**
* Posts a notification in the notification bar when a transition is
* detected. If the user clicks the notification, control goes to the main
* Activity.
*
* @param transitionType
* The type of transition that occurred.
*
*//*
private void sendNotification(String transitionType, String locationName) {
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent = new Intent(context, MainActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent = stackBuilder
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions
// >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context);
// Set the notification contents
builder.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(transitionType + ": " + locationName)
.setContentText(
context.getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}*/
@Override
public void onReceive(Context context, Intent intent)
{
GeofenceTransitionsIntentService.enqueueWork(context, intent);
}
}

View file

@ -1,7 +0,0 @@
package com.mapswithme.maps;
public interface GeofenceService
{
void registryGeofences();
void invalidateGeofences();
}

View file

@ -1,19 +0,0 @@
package com.mapswithme.maps;
class GeofenceServiceImpl implements GeofenceService
{
@Override
public void registryGeofences()
{
}
@Override
public void invalidateGeofences()
{
}
}

View file

@ -1,16 +0,0 @@
package com.mapswithme.maps;
class GeofenceServiceStub implements GeofenceService
{
@Override
public void registryGeofences()
{
}
@Override
public void invalidateGeofences()
{
}
}

View file

@ -3,14 +3,12 @@ package com.mapswithme.maps;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.CallSuper;
@ -31,12 +29,6 @@ import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.Toast;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.mapswithme.maps.Framework.MapObjectListener;
import com.mapswithme.maps.activity.CustomNavigateUpListener;
import com.mapswithme.maps.ads.LikesManager;
@ -74,7 +66,6 @@ import com.mapswithme.maps.editor.EditorHostFragment;
import com.mapswithme.maps.editor.FeatureCategoryActivity;
import com.mapswithme.maps.editor.ReportFragment;
import com.mapswithme.maps.gallery.Items;
import com.mapswithme.maps.geofence.GeoFenceFeature;
import com.mapswithme.maps.location.CompassData;
import com.mapswithme.maps.location.LocationHelper;
import com.mapswithme.maps.maplayer.MapLayerCompositeController;
@ -96,7 +87,6 @@ import com.mapswithme.maps.routing.RoutingBottomMenuListener;
import com.mapswithme.maps.routing.RoutingController;
import com.mapswithme.maps.routing.RoutingPlanFragment;
import com.mapswithme.maps.routing.RoutingPlanInplaceController;
import com.mapswithme.maps.scheduling.GeofenceTransitionsIntentService;
import com.mapswithme.maps.search.BookingFilterParams;
import com.mapswithme.maps.search.FilterActivity;
import com.mapswithme.maps.search.FloatingSearchToolbarController;
@ -144,11 +134,9 @@ import com.mapswithme.util.statistics.PlacePageTracker;
import com.mapswithme.util.statistics.Statistics;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
public class MwmActivity extends BaseMwmFragmentActivity
implements MapObjectListener,
@ -609,65 +597,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
}
initTips();
GeofencingClient geofencingClient = LocationServices.getGeofencingClient(this);
Location lastKnownLocation = LocationHelper.INSTANCE.getLastKnownLocation();
if (lastKnownLocation == null)
return;
List<GeoFenceFeature> adsFeatures = LightFramework.getLocalAdsFeatures(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), 50 * 1000, 20);
List<Geofence> geofences = new ArrayList<>();
Geofence geofence = new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(String.valueOf(System.currentTimeMillis()))
.setCircularRegion(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), 100)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setLoiteringDelay(5000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_DWELL)
.build();
geofences.add(geofence);
for (GeoFenceFeature each : adsFeatures)
{
}
GeofencingRequest geofencingRequest = getGeofencingRequest(geofences);
geofencingClient.addGeofences(geofencingRequest, getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>()
{
@Override
public void onSuccess(Void aVoid)
{
}
}).addOnFailureListener(new OnFailureListener()
{
@Override
public void onFailure(@NonNull Exception e)
{
}
});
}
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
PendingIntent mGeofencePendingIntent = null;
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}
private GeofencingRequest getGeofencingRequest(List<Geofence> mGeofenceList) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL);
builder.addGeofences(mGeofenceList);
return builder.build();
}
private void initViews()

View file

@ -19,6 +19,8 @@ import com.mapswithme.maps.bookmarks.data.BookmarkManager;
import com.mapswithme.maps.downloader.CountryItem;
import com.mapswithme.maps.downloader.MapManager;
import com.mapswithme.maps.editor.Editor;
import com.mapswithme.maps.location.GeofenceRegistry;
import com.mapswithme.maps.location.GeofenceRegistryImpl;
import com.mapswithme.maps.location.LocationHelper;
import com.mapswithme.maps.location.TrackRecorder;
import com.mapswithme.maps.maplayer.subway.SubwayManager;
@ -78,6 +80,9 @@ public class MwmApplication extends Application
@SuppressWarnings("NullableProblems")
@NonNull
private MediaPlayerWrapper mPlayer;
@SuppressWarnings("NullableProblems")
@NonNull
private GeofenceRegistry mGeofenceRegistry;
@NonNull
public SubwayManager getSubwayManager()
@ -165,6 +170,7 @@ public class MwmApplication extends Application
mPurchaseValidationObservable = new PurchaseValidationObservable();
mPlayer = new MediaPlayerWrapper(this);
mGeofenceRegistry = new GeofenceRegistryImpl(this);
}
private void initNotificationChannels()
@ -347,6 +353,12 @@ public class MwmApplication extends Application
return mPlayer;
}
@NonNull
public GeofenceRegistry getGeofenceRegistry()
{
return mGeofenceRegistry;
}
private native void nativeInitPlatform(String apkPath, String storagePath, String privatePath,
String tmpPath, String obbGooglePath, String flavorName,
String buildType, boolean isTablet);

View file

@ -1,11 +1,13 @@
package com.mapswithme.maps.geofence;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
/**
* Represents CampaignFeature from core.
*/
public class GeoFenceFeature
public class GeoFenceFeature implements Parcelable
{
private final long mwmVersion;
@NonNull
@ -71,4 +73,50 @@ public class GeoFenceFeature
result = 31 * result + featureIndex;
return result;
}
@NonNull
public String getId()
{
return String.valueOf(hashCode());
}
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeLong(this.mwmVersion);
dest.writeString(this.countryId);
dest.writeInt(this.featureIndex);
dest.writeDouble(this.latitude);
dest.writeDouble(this.longitude);
}
protected GeoFenceFeature(Parcel in)
{
this.mwmVersion = in.readLong();
this.countryId = in.readString();
this.featureIndex = in.readInt();
this.latitude = in.readDouble();
this.longitude = in.readDouble();
}
public static final Creator<GeoFenceFeature> CREATOR = new Creator<GeoFenceFeature>()
{
@Override
public GeoFenceFeature createFromParcel(Parcel source)
{
return new GeoFenceFeature(source);
}
@Override
public GeoFenceFeature[] newArray(int size)
{
return new GeoFenceFeature[size];
}
};
}

View file

@ -0,0 +1,41 @@
package com.mapswithme.maps.location;
public class GeofenceLocation
{
private final double mLat;
private final double mLon;
private final int mRadiusInMeters;
public GeofenceLocation(double lat, double lon, int radiusInMeters)
{
mLat = lat;
mLon = lon;
mRadiusInMeters = radiusInMeters;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("GeofenceLocation{");
sb.append("mLat=").append(getLat());
sb.append(", mLon=").append(getLon());
sb.append(", mRadiusInMeters=").append(getRadiusInMeters());
sb.append('}');
return sb.toString();
}
public double getLat()
{
return mLat;
}
public double getLon()
{
return mLon;
}
public int getRadiusInMeters()
{
return mRadiusInMeters;
}
}

View file

@ -0,0 +1,9 @@
package com.mapswithme.maps.location;
import android.support.annotation.NonNull;
public interface GeofenceRegistry
{
void registryGeofences(@NonNull GeofenceLocation location);
void invalidateGeofences();
}

View file

@ -0,0 +1,170 @@
package com.mapswithme.maps.location;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.annotation.NonNull;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.mapswithme.maps.GeofenceReceiver;
import com.mapswithme.maps.LightFramework;
import com.mapswithme.maps.geofence.GeoFenceFeature;
import com.mapswithme.util.PermissionsUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GeofenceRegistryImpl implements GeofenceRegistry
{
private static final int GEOFENCE_MAX_COUNT = 100;
private static final float PREFERRED_GEOFENCE_RADIUS = 125.0f;
@NonNull
private final Application mApplication;
@NonNull
private final List<GeofenceAndFeature> mGeofences;
@NonNull
private final GeofencingClient mGeofencingClient;
public GeofenceRegistryImpl(@NonNull Application application)
{
mGeofences = new ArrayList<>();
mApplication = application;
mGeofencingClient = LocationServices.getGeofencingClient(mApplication);
}
@Override
public void registryGeofences(@NonNull GeofenceLocation location)
{
checkThread();
checkPermission();
List<GeoFenceFeature> features = LightFramework.getLocalAdsFeatures(
location.getLat(), location.getLon(), location.getRadiusInMeters()/* from system location provider accuracy */, GEOFENCE_MAX_COUNT);
for (GeoFenceFeature each : features)
{
Geofence geofence = new Geofence.Builder()
.setRequestId(each.getId())
.setCircularRegion(each.getLatitude(), each.getLongitude(), PREFERRED_GEOFENCE_RADIUS)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER
| Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
mGeofences.add(new GeofenceAndFeature(geofence, each));
}
GeofencingRequest geofencingRequest = makeGeofencingRequest();
PendingIntent intent = makeGeofencePendingIntent();
mGeofencingClient.addGeofences(geofencingRequest, intent)
.addOnSuccessListener(params -> onAddSucceeded())
.addOnFailureListener(params -> onAddFailed());
}
@Override
public void invalidateGeofences()
{
checkThread();
checkPermission();
Iterator<GeofenceAndFeature> iterator = mGeofences.iterator();
List<String> expiredGeofences = new ArrayList<>();
while (iterator.hasNext())
{
GeofenceAndFeature current = iterator.next();
String requestId = current.getGeofence().getRequestId();
expiredGeofences.add(requestId);
}
mGeofencingClient.removeGeofences(expiredGeofences)
.addOnSuccessListener(params -> onRemoveFailed())
.addOnSuccessListener(params -> onRemoveSucceeded());
}
private void onAddSucceeded()
{
}
private void onAddFailed()
{
}
private void onRemoveSucceeded()
{
}
private void onRemoveFailed()
{
}
private void checkPermission()
{
if (!PermissionsUtils.isLocationGranted(mApplication))
throw new UnsupportedOperationException("Geofence registry required android.Manifest" +
".permission\n" +
" .ACCESS_FINE_LOCATION");
}
private static void checkThread()
{
if (!com.mapswithme.util.concurrency.UiThread.isUiThread())
throw new IllegalStateException("Must be call from Ui thread");
}
@NonNull
private PendingIntent makeGeofencePendingIntent() {
Intent intent = new Intent(mApplication, GeofenceReceiver.class);
return PendingIntent.getBroadcast(mApplication, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@NonNull
private GeofencingRequest makeGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
return builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL)
.addGeofences(collectGeofences())
.build();
}
@NonNull
private List<Geofence> collectGeofences()
{
List<Geofence> geofences = new ArrayList<>();
for (GeofenceAndFeature each : mGeofences)
{
geofences.add(each.getGeofence());
}
return geofences;
}
private static class GeofenceAndFeature
{
@NonNull
private final Geofence mGeofence;
@NonNull
private final GeoFenceFeature mFeature;
private GeofenceAndFeature(@NonNull Geofence geofence, @NonNull GeoFenceFeature feature)
{
mGeofence = geofence;
mFeature = feature;
}
@NonNull
public Geofence getGeofence()
{
return mGeofence;
}
@NonNull
public GeoFenceFeature getFeature()
{
return mFeature;
}
}
}

View file

@ -89,7 +89,7 @@ public class ConnectivityJobScheduler implements ConnectivityListener
.Builder(jobId, component)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.setMinimumLatency(1000 * 60 * 60)
.setMinimumLatency(TimeUnit.HOURS.toMillis(SCHEDULE_PERIOD_IN_HOURS))
.build();
mJobScheduler.schedule(jobInfo);
}

View file

@ -2,15 +2,22 @@ package com.mapswithme.maps.scheduling;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
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.location.LocationHelper;
public class GeofenceTransitionsIntentService extends JobIntentService
{
@NonNull
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
@Override
protected void onHandleWork(@NonNull Intent intent)
{
@ -21,20 +28,32 @@ public class GeofenceTransitionsIntentService extends JobIntentService
if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER
|| transitionType == Geofence.GEOFENCE_TRANSITION_EXIT)
{
Geofence geofence = geofencingEvent.getTriggeringGeofences().get(0);
mMainThreadHandler.post(new GeofencingEventTask(geofencingEvent));
// LightFramework.nativeLogLocalAdsEvent(1, /* myPlaceLat */, /* myPlaceLon */, /* locationProviderAccuracy */, , , );
}
}
/**
* Convenience method for enqueuing work in to this service.
*/
public static void enqueueWork(Context context, Intent intent) {
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
{
@NonNull
private final GeofencingEvent mGeofencingEvent;
GeofencingEventTask(@NonNull GeofencingEvent geofencingEvent)
{
mGeofencingEvent = geofencingEvent;
}
@Override
public void run()
{
Location lastKnownLocation = LocationHelper.INSTANCE.getLastKnownLocation();
double accuracy = lastKnownLocation == null ? mGeofencingEvent.getTriggeringLocation().getAccuracy()
: lastKnownLocation.getAccuracy();
}
}
}