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.server.location.LocationPermissions.PERMISSION_FINE;
23 
24 import android.annotation.Nullable;
25 import android.app.AppOpsManager;
26 import android.app.PendingIntent;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.location.Geofence;
30 import android.location.Location;
31 import android.location.LocationListener;
32 import android.location.LocationManager;
33 import android.location.LocationRequest;
34 import android.location.util.identity.CallerIdentity;
35 import android.os.Binder;
36 import android.os.PowerManager;
37 import android.os.SystemClock;
38 import android.os.WorkSource;
39 import android.stats.location.LocationStatsEnums;
40 import android.util.ArraySet;
41 
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.server.FgThread;
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<GeofenceManager.GeofenceKey, PendingIntent,
63                 GeofenceManager.GeofenceRegistration, 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     // geofencing unfortunately allows multiple geofences under the same pending intent, even though
77     // this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
78     // geofence) instead of (pendingintent).
79     static class GeofenceKey {
80 
81         private final PendingIntent mPendingIntent;
82         private final Geofence mGeofence;
83 
GeofenceKey(PendingIntent pendingIntent, Geofence geofence)84         GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
85             mPendingIntent = Objects.requireNonNull(pendingIntent);
86             mGeofence = Objects.requireNonNull(geofence);
87         }
88 
getPendingIntent()89         public PendingIntent getPendingIntent() {
90             return mPendingIntent;
91         }
92 
93         @Override
equals(Object o)94         public boolean equals(Object o) {
95             if (o instanceof GeofenceKey) {
96                 GeofenceKey that = (GeofenceKey) o;
97                 return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
98                         that.mGeofence);
99             }
100 
101             return false;
102         }
103 
104         @Override
hashCode()105         public int hashCode() {
106             return mPendingIntent.hashCode();
107         }
108     }
109 
110     protected class GeofenceRegistration extends
111             PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
112 
113         private static final int STATE_UNKNOWN = 0;
114         private static final int STATE_INSIDE = 1;
115         private static final int STATE_OUTSIDE = 2;
116 
117         private final Geofence mGeofence;
118         private final CallerIdentity mIdentity;
119         private final Location mCenter;
120         private final PowerManager.WakeLock mWakeLock;
121 
122         private int mGeofenceState;
123 
124         // we store these values because we don't trust the listeners not to give us dupes, not to
125         // spam us, and because checking the values may be more expensive
126         private boolean mPermitted;
127 
128         @Nullable private Location mCachedLocation;
129         private float mCachedLocationDistanceM;
130 
GeofenceRegistration(Geofence geofence, CallerIdentity identity, PendingIntent pendingIntent)131         GeofenceRegistration(Geofence geofence, CallerIdentity identity,
132                 PendingIntent pendingIntent) {
133             super(pendingIntent);
134 
135             mGeofence = geofence;
136             mIdentity = identity;
137             mCenter = new Location("");
138             mCenter.setLatitude(geofence.getLatitude());
139             mCenter.setLongitude(geofence.getLongitude());
140 
141             mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
142                     .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
143                             TAG + ":" + identity.getPackageName());
144             mWakeLock.setReferenceCounted(true);
145             mWakeLock.setWorkSource(identity.addToWorkSource(null));
146         }
147 
getGeofence()148         public Geofence getGeofence() {
149             return mGeofence;
150         }
151 
getIdentity()152         public CallerIdentity getIdentity() {
153             return mIdentity;
154         }
155 
156         @Override
getTag()157         public String getTag() {
158             return TAG;
159         }
160 
161         @Override
getPendingIntentFromKey(GeofenceKey geofenceKey)162         protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
163             return geofenceKey.getPendingIntent();
164         }
165 
166         @Override
getOwner()167         protected GeofenceManager getOwner() {
168             return GeofenceManager.this;
169         }
170 
171         @Override
onRegister()172         protected void onRegister() {
173             super.onRegister();
174 
175             mGeofenceState = STATE_UNKNOWN;
176             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
177                     mIdentity);
178         }
179 
180         @Override
onActive()181         protected void onActive() {
182             Location location = getLastLocation();
183             if (location != null) {
184                 executeOperation(onLocationChanged(location));
185             }
186         }
187 
isPermitted()188         boolean isPermitted() {
189             return mPermitted;
190         }
191 
onLocationPermissionsChanged(@ullable String packageName)192         boolean onLocationPermissionsChanged(@Nullable String packageName) {
193             if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
194                 return onLocationPermissionsChanged();
195             }
196 
197             return false;
198         }
199 
onLocationPermissionsChanged(int uid)200         boolean onLocationPermissionsChanged(int uid) {
201             if (mIdentity.getUid() == uid) {
202                 return onLocationPermissionsChanged();
203             }
204 
205             return false;
206         }
207 
onLocationPermissionsChanged()208         private boolean onLocationPermissionsChanged() {
209             boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
210                     mIdentity);
211             if (permitted != mPermitted) {
212                 mPermitted = permitted;
213                 return true;
214             }
215 
216             return false;
217         }
218 
getDistanceToBoundary(Location location)219         double getDistanceToBoundary(Location location) {
220             if (!location.equals(mCachedLocation)) {
221                 mCachedLocation = location;
222                 mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
223             }
224 
225             return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
226         }
227 
onLocationChanged(Location location)228         ListenerOperation<PendingIntent> onLocationChanged(Location location) {
229             // remove expired fences
230             if (mGeofence.isExpired()) {
231                 remove();
232                 return null;
233             }
234 
235             mCachedLocation = location;
236             mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
237 
238             int oldState = mGeofenceState;
239             float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
240             if (mCachedLocationDistanceM <= radius) {
241                 mGeofenceState = STATE_INSIDE;
242                 if (oldState != STATE_INSIDE) {
243                     return pendingIntent -> sendIntent(pendingIntent, true);
244                 }
245             } else {
246                 mGeofenceState = STATE_OUTSIDE;
247                 if (oldState == STATE_INSIDE) {
248                     // return exit only if previously entered
249                     return pendingIntent -> sendIntent(pendingIntent, false);
250                 }
251             }
252 
253             return null;
254         }
255 
sendIntent(PendingIntent pendingIntent, boolean entering)256         private void sendIntent(PendingIntent pendingIntent, boolean entering) {
257             Intent intent = new Intent().putExtra(KEY_PROXIMITY_ENTERING, entering);
258 
259             mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
260             try {
261                 // send() only enforces permissions for broadcast intents, but since clients can
262                 // select any kind of pending intent we do not rely on send() to enforce permissions
263                 pendingIntent.send(mContext, 0, intent, (pI, i, rC, rD, rE) -> mWakeLock.release(),
264                         null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
265             } catch (PendingIntent.CanceledException e) {
266                 mWakeLock.release();
267                 removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
268             }
269         }
270 
271         @Override
toString()272         public String toString() {
273             StringBuilder builder = new StringBuilder();
274             builder.append(mIdentity);
275 
276             ArraySet<String> flags = new ArraySet<>(1);
277             if (!mPermitted) {
278                 flags.add("na");
279             }
280             if (!flags.isEmpty()) {
281                 builder.append(" ").append(flags);
282             }
283 
284             builder.append(" ").append(mGeofence);
285             return builder.toString();
286         }
287     }
288 
289     final Object mLock = new Object();
290 
291     protected final Context mContext;
292 
293     private final UserListener mUserChangedListener = this::onUserChanged;
294     private final SettingsHelper.UserSettingChangedListener mLocationEnabledChangedListener =
295             this::onLocationEnabledChanged;
296     private final SettingsHelper.UserSettingChangedListener
297             mLocationPackageBlacklistChangedListener =
298             this::onLocationPackageBlacklistChanged;
299     private final LocationPermissionsHelper.LocationPermissionsListener
300             mLocationPermissionsListener =
301             new LocationPermissionsHelper.LocationPermissionsListener() {
302                 @Override
303                 public void onLocationPermissionsChanged(@Nullable String packageName) {
304                     GeofenceManager.this.onLocationPermissionsChanged(packageName);
305                 }
306 
307                 @Override
308                 public void onLocationPermissionsChanged(int uid) {
309                     GeofenceManager.this.onLocationPermissionsChanged(uid);
310                 }
311             };
312 
313     protected final UserInfoHelper mUserInfoHelper;
314     protected final LocationPermissionsHelper mLocationPermissionsHelper;
315     protected final SettingsHelper mSettingsHelper;
316     protected final LocationUsageLogger mLocationUsageLogger;
317 
318     @GuardedBy("mLock")
319     @Nullable private LocationManager mLocationManager;
320 
321     @GuardedBy("mLock")
322     @Nullable private Location mLastLocation;
323 
GeofenceManager(Context context, Injector injector)324     public GeofenceManager(Context context, Injector injector) {
325         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
326         mUserInfoHelper = injector.getUserInfoHelper();
327         mSettingsHelper = injector.getSettingsHelper();
328         mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
329         mLocationUsageLogger = injector.getLocationUsageLogger();
330     }
331 
getLocationManager()332     private LocationManager getLocationManager() {
333         synchronized (mLock) {
334             if (mLocationManager == null) {
335                 mLocationManager = Objects.requireNonNull(
336                         mContext.getSystemService(LocationManager.class));
337             }
338 
339             return mLocationManager;
340         }
341     }
342 
343     /**
344      * Adds a new geofence, replacing any geofence already associated with the PendingIntent. It
345      * doesn't make any real sense to register multiple geofences with the same pending intent, but
346      * we continue to allow this for backwards compatibility.
347      */
addGeofence(Geofence geofence, PendingIntent pendingIntent, String packageName, @Nullable String attributionTag)348     public void addGeofence(Geofence geofence, PendingIntent pendingIntent, String packageName,
349             @Nullable String attributionTag) {
350         LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_FINE);
351 
352         CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName,
353                 attributionTag, AppOpsManager.toReceiverId(pendingIntent));
354 
355         final long ident = Binder.clearCallingIdentity();
356         try {
357             putRegistration(new GeofenceKey(pendingIntent, geofence),
358                     new GeofenceRegistration(geofence, identity, pendingIntent));
359         } finally {
360             Binder.restoreCallingIdentity(ident);
361         }
362     }
363 
364     /**
365      * Removes the geofence associated with the PendingIntent.
366      */
removeGeofence(PendingIntent pendingIntent)367     public void removeGeofence(PendingIntent pendingIntent) {
368         final long identity = Binder.clearCallingIdentity();
369         try {
370             removeRegistrationIf(key -> key.getPendingIntent().equals(pendingIntent));
371         } finally {
372             Binder.restoreCallingIdentity(identity);
373         }
374     }
375 
376     @Override
isActive(GeofenceRegistration registration)377     protected boolean isActive(GeofenceRegistration registration) {
378         return registration.isPermitted() && isActive(registration.getIdentity());
379     }
380 
isActive(CallerIdentity identity)381     private boolean isActive(CallerIdentity identity) {
382         if (identity.isSystemServer()) {
383             if (!mSettingsHelper.isLocationEnabled(mUserInfoHelper.getCurrentUserId())) {
384                 return false;
385             }
386         } else {
387             if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
388                 return false;
389             }
390             if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
391                 return false;
392             }
393             if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
394                     identity.getPackageName())) {
395                 return false;
396             }
397         }
398 
399         return true;
400     }
401 
402     @Override
onRegister()403     protected void onRegister() {
404         mUserInfoHelper.addListener(mUserChangedListener);
405         mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
406         mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
407                 mLocationPackageBlacklistChangedListener);
408         mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
409     }
410 
411     @Override
onUnregister()412     protected void onUnregister() {
413         mUserInfoHelper.removeListener(mUserChangedListener);
414         mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
415         mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
416                 mLocationPackageBlacklistChangedListener);
417         mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
418     }
419 
420     @Override
onRegistrationAdded(GeofenceKey key, GeofenceRegistration registration)421     protected void onRegistrationAdded(GeofenceKey key, GeofenceRegistration registration) {
422         mLocationUsageLogger.logLocationApiUsage(
423                 LocationStatsEnums.USAGE_ENDED,
424                 LocationStatsEnums.API_REQUEST_GEOFENCE,
425                 registration.getIdentity().getPackageName(),
426                 registration.getIdentity().getAttributionTag(),
427                 null,
428                 /* LocationRequest= */ null,
429                 /* hasListener= */ false,
430                 true,
431                 registration.getGeofence(), true);
432     }
433 
434     @Override
onRegistrationRemoved(GeofenceKey key, GeofenceRegistration registration)435     protected void onRegistrationRemoved(GeofenceKey key, GeofenceRegistration registration) {
436         mLocationUsageLogger.logLocationApiUsage(
437                 LocationStatsEnums.USAGE_ENDED,
438                 LocationStatsEnums.API_REQUEST_GEOFENCE,
439                 registration.getIdentity().getPackageName(),
440                 registration.getIdentity().getAttributionTag(),
441                 null,
442                 /* LocationRequest= */ null,
443                 /* hasListener= */ false,
444                 true,
445                 registration.getGeofence(), true);
446     }
447 
448     @Override
registerWithService(LocationRequest locationRequest, Collection<GeofenceRegistration> registrations)449     protected boolean registerWithService(LocationRequest locationRequest,
450             Collection<GeofenceRegistration> registrations) {
451         getLocationManager().requestLocationUpdates(FUSED_PROVIDER, locationRequest,
452                 FgThread.getExecutor(), this);
453         return true;
454     }
455 
456     @Override
unregisterWithService()457     protected void unregisterWithService() {
458         synchronized (mLock) {
459             getLocationManager().removeUpdates(this);
460             mLastLocation = null;
461         }
462     }
463 
464     @Override
mergeRegistrations(Collection<GeofenceRegistration> registrations)465     protected LocationRequest mergeRegistrations(Collection<GeofenceRegistration> registrations) {
466         Location location = getLastLocation();
467 
468         long realtimeMs = SystemClock.elapsedRealtime();
469 
470         WorkSource workSource = null;
471         double minFenceDistanceM = Double.MAX_VALUE;
472         for (GeofenceRegistration registration : registrations) {
473             if (registration.getGeofence().isExpired(realtimeMs)) {
474                 continue;
475             }
476 
477             workSource = registration.getIdentity().addToWorkSource(workSource);
478 
479             if (location != null) {
480                 double fenceDistanceM = registration.getDistanceToBoundary(location);
481                 if (fenceDistanceM < minFenceDistanceM) {
482                     minFenceDistanceM = fenceDistanceM;
483                 }
484             }
485         }
486 
487         long intervalMs;
488         if (Double.compare(minFenceDistanceM, Double.MAX_VALUE) < 0) {
489             intervalMs = (long) Math.min(MAX_LOCATION_INTERVAL_MS,
490                     Math.max(
491                             mSettingsHelper.getBackgroundThrottleProximityAlertIntervalMs(),
492                             minFenceDistanceM * 1000 / MAX_SPEED_M_S));
493         } else {
494             intervalMs = mSettingsHelper.getBackgroundThrottleProximityAlertIntervalMs();
495         }
496 
497         return new LocationRequest.Builder(intervalMs)
498                 .setMinUpdateIntervalMillis(0)
499                 .setHiddenFromAppOps(true)
500                 .setWorkSource(workSource)
501                 .build();
502     }
503 
504 
505     @Override
onLocationChanged(Location location)506     public void onLocationChanged(Location location) {
507         synchronized (mLock) {
508             mLastLocation = location;
509         }
510 
511         deliverToListeners(registration -> {
512             return registration.onLocationChanged(location);
513         });
514         updateService();
515     }
516 
getLastLocation()517     @Nullable Location getLastLocation() {
518         Location location;
519         synchronized (mLock) {
520             location = mLastLocation;
521         }
522 
523         if (location == null) {
524             location = getLocationManager().getLastLocation();
525         }
526 
527         if (location != null) {
528             if (location.getElapsedRealtimeAgeMillis() > MAX_LOCATION_AGE_MS) {
529                 location = null;
530             }
531         }
532 
533         return location;
534     }
535 
onUserChanged(int userId, int change)536     void onUserChanged(int userId, int change) {
537         // current user changes affect whether system server location requests are allowed to access
538         // location, and visibility changes affect whether any given user may access location.
539         if (change == UserListener.CURRENT_USER_CHANGED
540                 || change == UserListener.USER_VISIBILITY_CHANGED) {
541             updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
542         }
543     }
544 
onLocationEnabledChanged(int userId)545     void onLocationEnabledChanged(int userId) {
546         updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
547     }
548 
onLocationPackageBlacklistChanged(int userId)549     void onLocationPackageBlacklistChanged(int userId) {
550         updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
551     }
552 
onLocationPermissionsChanged(@ullable String packageName)553     void onLocationPermissionsChanged(@Nullable String packageName) {
554         updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
555     }
556 
onLocationPermissionsChanged(int uid)557     void onLocationPermissionsChanged(int uid) {
558         updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
559     }
560 }
561