• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.location.geofence;
18 
19 import static android.location.LocationManager.FUSED_PROVIDER;
20 import static android.location.LocationManager.KEY_PROXIMITY_ENTERING;
21 
22 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
23 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
24 
25 import android.annotation.Nullable;
26 import android.app.AppOpsManager;
27 import android.app.PendingIntent;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.location.Geofence;
31 import android.location.Location;
32 import android.location.LocationListener;
33 import android.location.LocationManager;
34 import android.location.LocationRequest;
35 import android.location.util.identity.CallerIdentity;
36 import android.os.Binder;
37 import android.os.PowerManager;
38 import android.os.SystemClock;
39 import android.os.WorkSource;
40 import android.stats.location.LocationStatsEnums;
41 import android.util.ArraySet;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.server.PendingIntentUtils;
45 import com.android.server.location.LocationPermissions;
46 import com.android.server.location.injector.Injector;
47 import com.android.server.location.injector.LocationPermissionsHelper;
48 import com.android.server.location.injector.LocationUsageLogger;
49 import com.android.server.location.injector.SettingsHelper;
50 import com.android.server.location.injector.UserInfoHelper;
51 import com.android.server.location.injector.UserInfoHelper.UserListener;
52 import com.android.server.location.listeners.ListenerMultiplexer;
53 import com.android.server.location.listeners.PendingIntentListenerRegistration;
54 
55 import java.util.Collection;
56 import java.util.Objects;
57 
58 /**
59  * Manages all geofences.
60  */
61 public class GeofenceManager extends
62         ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
63                 LocationRequest> implements
64         LocationListener {
65 
66     private static final String TAG = "GeofenceManager";
67 
68     private static final String ATTRIBUTION_TAG = "GeofencingService";
69 
70     private static final long WAKELOCK_TIMEOUT_MS = 30000;
71 
72     private static final int MAX_SPEED_M_S = 100;  // 360 km/hr (high speed train)
73     private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
74     private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
75 
76     protected final class GeofenceRegistration extends
77             PendingIntentListenerRegistration<Geofence, PendingIntent> {
78 
79         private static final int STATE_UNKNOWN = 0;
80         private static final int STATE_INSIDE = 1;
81         private static final int STATE_OUTSIDE = 2;
82 
83         private final Location mCenter;
84         private final PowerManager.WakeLock mWakeLock;
85 
86         private int mGeofenceState;
87 
88         // we store these values because we don't trust the listeners not to give us dupes, not to
89         // spam us, and because checking the values may be more expensive
90         private boolean mPermitted;
91 
92         private @Nullable Location mCachedLocation;
93         private float mCachedLocationDistanceM;
94 
GeofenceRegistration(Geofence geofence, CallerIdentity identity, PendingIntent pendingIntent)95         protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
96                 PendingIntent pendingIntent) {
97             super(geofence, identity, pendingIntent);
98 
99             mCenter = new Location("");
100             mCenter.setLatitude(geofence.getLatitude());
101             mCenter.setLongitude(geofence.getLongitude());
102 
103             mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
104                     .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
105                             TAG + ":" + identity.getPackageName());
106             mWakeLock.setReferenceCounted(true);
107             mWakeLock.setWorkSource(identity.addToWorkSource(null));
108         }
109 
110         @Override
getOwner()111         protected GeofenceManager getOwner() {
112             return GeofenceManager.this;
113         }
114 
115         @Override
onPendingIntentListenerRegister()116         protected void onPendingIntentListenerRegister() {
117             mGeofenceState = STATE_UNKNOWN;
118             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
119                     getIdentity());
120         }
121 
122         @Override
onActive()123         protected void onActive() {
124             Location location = getLastLocation();
125             if (location != null) {
126                 executeOperation(onLocationChanged(location));
127             }
128         }
129 
isPermitted()130         boolean isPermitted() {
131             return mPermitted;
132         }
133 
onLocationPermissionsChanged(String packageName)134         boolean onLocationPermissionsChanged(String packageName) {
135             if (getIdentity().getPackageName().equals(packageName)) {
136                 return onLocationPermissionsChanged();
137             }
138 
139             return false;
140         }
141 
onLocationPermissionsChanged(int uid)142         boolean onLocationPermissionsChanged(int uid) {
143             if (getIdentity().getUid() == uid) {
144                 return onLocationPermissionsChanged();
145             }
146 
147             return false;
148         }
149 
onLocationPermissionsChanged()150         private boolean onLocationPermissionsChanged() {
151             boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
152                     getIdentity());
153             if (permitted != mPermitted) {
154                 mPermitted = permitted;
155                 return true;
156             }
157 
158             return false;
159         }
160 
getDistanceToBoundary(Location location)161         double getDistanceToBoundary(Location location) {
162             if (!location.equals(mCachedLocation)) {
163                 mCachedLocation = location;
164                 mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
165             }
166 
167             return Math.abs(getRequest().getRadius() - mCachedLocationDistanceM);
168         }
169 
onLocationChanged(Location location)170         ListenerOperation<PendingIntent> onLocationChanged(Location location) {
171             // remove expired fences
172             if (getRequest().isExpired()) {
173                 remove();
174                 return null;
175             }
176 
177             mCachedLocation = location;
178             mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
179 
180             int oldState = mGeofenceState;
181             float radius = Math.max(getRequest().getRadius(), location.getAccuracy());
182             if (mCachedLocationDistanceM <= radius) {
183                 mGeofenceState = STATE_INSIDE;
184                 if (oldState != STATE_INSIDE) {
185                     return pendingIntent -> sendIntent(pendingIntent, true);
186                 }
187             } else {
188                 mGeofenceState = STATE_OUTSIDE;
189                 if (oldState == STATE_INSIDE) {
190                     // return exit only if previously entered
191                     return pendingIntent -> sendIntent(pendingIntent, false);
192                 }
193             }
194 
195             return null;
196         }
197 
sendIntent(PendingIntent pendingIntent, boolean entering)198         private void sendIntent(PendingIntent pendingIntent, boolean entering) {
199             Intent intent = new Intent().putExtra(KEY_PROXIMITY_ENTERING, entering);
200 
201             mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
202             try {
203                 // send() only enforces permissions for broadcast intents, but since clients can
204                 // select any kind of pending intent we do not rely on send() to enforce permissions
205                 pendingIntent.send(mContext, 0, intent, (pI, i, rC, rD, rE) -> mWakeLock.release(),
206                         null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
207             } catch (PendingIntent.CanceledException e) {
208                 mWakeLock.release();
209                 removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
210             }
211         }
212 
213         @Override
toString()214         public String toString() {
215             StringBuilder builder = new StringBuilder();
216             builder.append(getIdentity());
217 
218             ArraySet<String> flags = new ArraySet<>(1);
219             if (!mPermitted) {
220                 flags.add("na");
221             }
222             if (!flags.isEmpty()) {
223                 builder.append(" ").append(flags);
224             }
225 
226             builder.append(" ").append(getRequest());
227             return builder.toString();
228         }
229     }
230 
231     final Object mLock = new Object();
232 
233     protected final Context mContext;
234 
235     private final UserListener mUserChangedListener = this::onUserChanged;
236     private final SettingsHelper.UserSettingChangedListener mLocationEnabledChangedListener =
237             this::onLocationEnabledChanged;
238     private final SettingsHelper.UserSettingChangedListener
239             mLocationPackageBlacklistChangedListener =
240             this::onLocationPackageBlacklistChanged;
241     private final LocationPermissionsHelper.LocationPermissionsListener
242             mLocationPermissionsListener =
243             new LocationPermissionsHelper.LocationPermissionsListener() {
244                 @Override
245                 public void onLocationPermissionsChanged(String packageName) {
246                     GeofenceManager.this.onLocationPermissionsChanged(packageName);
247                 }
248 
249                 @Override
250                 public void onLocationPermissionsChanged(int uid) {
251                     GeofenceManager.this.onLocationPermissionsChanged(uid);
252                 }
253             };
254 
255     protected final UserInfoHelper mUserInfoHelper;
256     protected final LocationPermissionsHelper mLocationPermissionsHelper;
257     protected final SettingsHelper mSettingsHelper;
258     protected final LocationUsageLogger mLocationUsageLogger;
259 
260     @GuardedBy("mLock")
261     private @Nullable LocationManager mLocationManager;
262 
263     @GuardedBy("mLock")
264     private @Nullable Location mLastLocation;
265 
GeofenceManager(Context context, Injector injector)266     public GeofenceManager(Context context, Injector injector) {
267         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
268         mUserInfoHelper = injector.getUserInfoHelper();
269         mSettingsHelper = injector.getSettingsHelper();
270         mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
271         mLocationUsageLogger = injector.getLocationUsageLogger();
272     }
273 
274     @Override
getTag()275     public String getTag() {
276         return TAG;
277     }
278 
getLocationManager()279     private LocationManager getLocationManager() {
280         synchronized (mLock) {
281             if (mLocationManager == null) {
282                 mLocationManager = Objects.requireNonNull(
283                         mContext.getSystemService(LocationManager.class));
284             }
285 
286             return mLocationManager;
287         }
288     }
289 
290     /**
291      * Adds a new geofence, replacing any geofence already associated with the PendingIntent. It
292      * doesn't make any real sense to register multiple geofences with the same pending intent, but
293      * we continue to allow this for backwards compatibility.
294      */
addGeofence(Geofence geofence, PendingIntent pendingIntent, String packageName, @Nullable String attributionTag)295     public void addGeofence(Geofence geofence, PendingIntent pendingIntent, String packageName,
296             @Nullable String attributionTag) {
297         LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_FINE);
298 
299         CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName,
300                 attributionTag, AppOpsManager.toReceiverId(pendingIntent));
301 
302         final long ident = Binder.clearCallingIdentity();
303         try {
304             putRegistration(new GeofenceKey(pendingIntent, geofence),
305                     new GeofenceRegistration(geofence, identity, pendingIntent));
306         } finally {
307             Binder.restoreCallingIdentity(ident);
308         }
309     }
310 
311     /**
312      * Removes the geofence associated with the PendingIntent.
313      */
removeGeofence(PendingIntent pendingIntent)314     public void removeGeofence(PendingIntent pendingIntent) {
315         final long identity = Binder.clearCallingIdentity();
316         try {
317             removeRegistrationIf(key -> key.getPendingIntent().equals(pendingIntent));
318         } finally {
319             Binder.restoreCallingIdentity(identity);
320         }
321     }
322 
323     @Override
isActive(GeofenceRegistration registration)324     protected boolean isActive(GeofenceRegistration registration) {
325         return registration.isPermitted() && isActive(registration.getIdentity());
326     }
327 
isActive(CallerIdentity identity)328     private boolean isActive(CallerIdentity identity) {
329         if (identity.isSystemServer()) {
330             if (!mSettingsHelper.isLocationEnabled(mUserInfoHelper.getCurrentUserId())) {
331                 return false;
332             }
333         } else {
334             if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
335                 return false;
336             }
337             if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
338                 return false;
339             }
340             if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
341                     identity.getPackageName())) {
342                 return false;
343             }
344         }
345 
346         return true;
347     }
348 
349     @Override
onRegister()350     protected void onRegister() {
351         mUserInfoHelper.addListener(mUserChangedListener);
352         mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
353         mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
354                 mLocationPackageBlacklistChangedListener);
355         mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
356     }
357 
358     @Override
onUnregister()359     protected void onUnregister() {
360         mUserInfoHelper.removeListener(mUserChangedListener);
361         mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
362         mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
363                 mLocationPackageBlacklistChangedListener);
364         mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
365     }
366 
367     @Override
onRegistrationAdded(GeofenceKey key, GeofenceRegistration registration)368     protected void onRegistrationAdded(GeofenceKey key, GeofenceRegistration registration) {
369         mLocationUsageLogger.logLocationApiUsage(
370                 LocationStatsEnums.USAGE_ENDED,
371                 LocationStatsEnums.API_REQUEST_GEOFENCE,
372                 registration.getIdentity().getPackageName(),
373                 registration.getIdentity().getAttributionTag(),
374                 null,
375                 /* LocationRequest= */ null,
376                 /* hasListener= */ false,
377                 true,
378                 registration.getRequest(), true);
379     }
380 
381     @Override
onRegistrationRemoved(GeofenceKey key, GeofenceRegistration registration)382     protected void onRegistrationRemoved(GeofenceKey key, GeofenceRegistration registration) {
383         mLocationUsageLogger.logLocationApiUsage(
384                 LocationStatsEnums.USAGE_ENDED,
385                 LocationStatsEnums.API_REQUEST_GEOFENCE,
386                 registration.getIdentity().getPackageName(),
387                 registration.getIdentity().getAttributionTag(),
388                 null,
389                 /* LocationRequest= */ null,
390                 /* hasListener= */ false,
391                 true,
392                 registration.getRequest(), true);
393     }
394 
395     @Override
registerWithService(LocationRequest locationRequest, Collection<GeofenceRegistration> registrations)396     protected boolean registerWithService(LocationRequest locationRequest,
397             Collection<GeofenceRegistration> registrations) {
398         getLocationManager().requestLocationUpdates(FUSED_PROVIDER, locationRequest,
399                 DIRECT_EXECUTOR, this);
400         return true;
401     }
402 
403     @Override
unregisterWithService()404     protected void unregisterWithService() {
405         synchronized (mLock) {
406             getLocationManager().removeUpdates(this);
407             mLastLocation = null;
408         }
409     }
410 
411     @Override
mergeRegistrations(Collection<GeofenceRegistration> registrations)412     protected LocationRequest mergeRegistrations(Collection<GeofenceRegistration> registrations) {
413         Location location = getLastLocation();
414 
415         long realtimeMs = SystemClock.elapsedRealtime();
416 
417         WorkSource workSource = null;
418         double minFenceDistanceM = Double.MAX_VALUE;
419         for (GeofenceRegistration registration : registrations) {
420             if (registration.getRequest().isExpired(realtimeMs)) {
421                 continue;
422             }
423 
424             workSource = registration.getIdentity().addToWorkSource(workSource);
425 
426             if (location != null) {
427                 double fenceDistanceM = registration.getDistanceToBoundary(location);
428                 if (fenceDistanceM < minFenceDistanceM) {
429                     minFenceDistanceM = fenceDistanceM;
430                 }
431             }
432         }
433 
434         long intervalMs;
435         if (Double.compare(minFenceDistanceM, Double.MAX_VALUE) < 0) {
436             intervalMs = (long) Math.min(MAX_LOCATION_INTERVAL_MS,
437                     Math.max(
438                             mSettingsHelper.getBackgroundThrottleProximityAlertIntervalMs(),
439                             minFenceDistanceM * 1000 / MAX_SPEED_M_S));
440         } else {
441             intervalMs = mSettingsHelper.getBackgroundThrottleProximityAlertIntervalMs();
442         }
443 
444         return new LocationRequest.Builder(intervalMs)
445                 .setMinUpdateIntervalMillis(0)
446                 .setHiddenFromAppOps(true)
447                 .setWorkSource(workSource)
448                 .build();
449     }
450 
451 
452     @Override
onLocationChanged(Location location)453     public void onLocationChanged(Location location) {
454         synchronized (mLock) {
455             mLastLocation = location;
456         }
457 
458         deliverToListeners(registration -> {
459             return registration.onLocationChanged(location);
460         });
461         updateService();
462     }
463 
getLastLocation()464     @Nullable Location getLastLocation() {
465         Location location;
466         synchronized (mLock) {
467             location = mLastLocation;
468         }
469 
470         if (location == null) {
471             location = getLocationManager().getLastLocation();
472         }
473 
474         if (location != null) {
475             if (location.getElapsedRealtimeAgeMillis() > MAX_LOCATION_AGE_MS) {
476                 location = null;
477             }
478         }
479 
480         return location;
481     }
482 
onUserChanged(int userId, int change)483     void onUserChanged(int userId, int change) {
484         if (change == UserListener.CURRENT_USER_CHANGED) {
485             updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
486         }
487     }
488 
onLocationEnabledChanged(int userId)489     void onLocationEnabledChanged(int userId) {
490         updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
491     }
492 
onLocationPackageBlacklistChanged(int userId)493     void onLocationPackageBlacklistChanged(int userId) {
494         updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
495     }
496 
onLocationPermissionsChanged(String packageName)497     void onLocationPermissionsChanged(String packageName) {
498         updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
499     }
500 
onLocationPermissionsChanged(int uid)501     void onLocationPermissionsChanged(int uid) {
502         updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
503     }
504 }
505