/services/java/com/android/server/location/GeofenceManager.java

https://github.com/aizuzi/platform_frameworks_base · Java · 440 lines · 294 code · 59 blank · 87 comment · 58 complexity · e80e69049f0316e3c96deaac988d4167 MD5 · raw file

  1. /*
  2. * Copyright (C) 2012 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. package com.android.server.location;
  17. import java.io.PrintWriter;
  18. import java.util.Iterator;
  19. import java.util.LinkedList;
  20. import java.util.List;
  21. import android.app.AppOpsManager;
  22. import android.app.PendingIntent;
  23. import android.content.Context;
  24. import android.content.Intent;
  25. import android.location.Geofence;
  26. import android.location.Location;
  27. import android.location.LocationListener;
  28. import android.location.LocationManager;
  29. import android.location.LocationRequest;
  30. import android.os.Bundle;
  31. import android.os.Handler;
  32. import android.os.Message;
  33. import android.os.PowerManager;
  34. import android.os.SystemClock;
  35. import android.util.Slog;
  36. import com.android.server.LocationManagerService;
  37. public class GeofenceManager implements LocationListener, PendingIntent.OnFinished {
  38. private static final String TAG = "GeofenceManager";
  39. private static final boolean D = LocationManagerService.D;
  40. private static final int MSG_UPDATE_FENCES = 1;
  41. /**
  42. * Assume a maximum land speed, as a heuristic to throttle location updates.
  43. * (Air travel should result in an airplane mode toggle which will
  44. * force a new location update anyway).
  45. */
  46. private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train)
  47. /**
  48. * Maximum age after which a location is no longer considered fresh enough to use.
  49. */
  50. private static final long MAX_AGE_NANOS = 5 * 60 * 1000000000L; // five minutes
  51. /**
  52. * Most frequent update interval allowed.
  53. */
  54. private static final long MIN_INTERVAL_MS = 1 * 60 * 1000; // one minute
  55. /**
  56. * Least frequent update interval allowed.
  57. */
  58. private static final long MAX_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
  59. private final Context mContext;
  60. private final LocationManager mLocationManager;
  61. private final AppOpsManager mAppOps;
  62. private final PowerManager.WakeLock mWakeLock;
  63. private final GeofenceHandler mHandler;
  64. private final LocationBlacklist mBlacklist;
  65. private Object mLock = new Object();
  66. // access to members below is synchronized on mLock
  67. /**
  68. * A list containing all registered geofences.
  69. */
  70. private List<GeofenceState> mFences = new LinkedList<GeofenceState>();
  71. /**
  72. * This is set true when we have an active request for {@link Location} updates via
  73. * {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener,
  74. * android.os.Looper).
  75. */
  76. private boolean mReceivingLocationUpdates;
  77. /**
  78. * The update interval component of the current active {@link Location} update request.
  79. */
  80. private long mLocationUpdateInterval;
  81. /**
  82. * The {@link Location} most recently received via {@link #onLocationChanged(Location)}.
  83. */
  84. private Location mLastLocationUpdate;
  85. /**
  86. * This is set true when a {@link Location} is received via
  87. * {@link #onLocationChanged(Location)} or {@link #scheduleUpdateFencesLocked()}, and cleared
  88. * when that Location has been processed via {@link #updateFences()}
  89. */
  90. private boolean mPendingUpdate;
  91. public GeofenceManager(Context context, LocationBlacklist blacklist) {
  92. mContext = context;
  93. mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
  94. mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
  95. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
  96. mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
  97. mHandler = new GeofenceHandler();
  98. mBlacklist = blacklist;
  99. }
  100. public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
  101. int allowedResolutionLevel, int uid, String packageName) {
  102. if (D) {
  103. Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
  104. + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
  105. }
  106. GeofenceState state = new GeofenceState(geofence,
  107. request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
  108. synchronized (mLock) {
  109. // first make sure it doesn't already exist
  110. for (int i = mFences.size() - 1; i >= 0; i--) {
  111. GeofenceState w = mFences.get(i);
  112. if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) {
  113. // already exists, remove the old one
  114. mFences.remove(i);
  115. break;
  116. }
  117. }
  118. mFences.add(state);
  119. scheduleUpdateFencesLocked();
  120. }
  121. }
  122. public void removeFence(Geofence fence, PendingIntent intent) {
  123. if (D) {
  124. Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent);
  125. }
  126. synchronized (mLock) {
  127. Iterator<GeofenceState> iter = mFences.iterator();
  128. while (iter.hasNext()) {
  129. GeofenceState state = iter.next();
  130. if (state.mIntent.equals(intent)) {
  131. if (fence == null) {
  132. // always remove
  133. iter.remove();
  134. } else {
  135. // just remove matching fences
  136. if (fence.equals(state.mFence)) {
  137. iter.remove();
  138. }
  139. }
  140. }
  141. }
  142. scheduleUpdateFencesLocked();
  143. }
  144. }
  145. public void removeFence(String packageName) {
  146. if (D) {
  147. Slog.d(TAG, "removeFence: packageName=" + packageName);
  148. }
  149. synchronized (mLock) {
  150. Iterator<GeofenceState> iter = mFences.iterator();
  151. while (iter.hasNext()) {
  152. GeofenceState state = iter.next();
  153. if (state.mPackageName.equals(packageName)) {
  154. iter.remove();
  155. }
  156. }
  157. scheduleUpdateFencesLocked();
  158. }
  159. }
  160. private void removeExpiredFencesLocked() {
  161. long time = SystemClock.elapsedRealtime();
  162. Iterator<GeofenceState> iter = mFences.iterator();
  163. while (iter.hasNext()) {
  164. GeofenceState state = iter.next();
  165. if (state.mExpireAt < time) {
  166. iter.remove();
  167. }
  168. }
  169. }
  170. private void scheduleUpdateFencesLocked() {
  171. if (!mPendingUpdate) {
  172. mPendingUpdate = true;
  173. mHandler.sendEmptyMessage(MSG_UPDATE_FENCES);
  174. }
  175. }
  176. /**
  177. * Returns the location received most recently from {@link #onLocationChanged(Location)},
  178. * or consult {@link LocationManager#getLastLocation()} if none has arrived. Does not return
  179. * either if the location would be too stale to be useful.
  180. *
  181. * @return a fresh, valid Location, or null if none is available
  182. */
  183. private Location getFreshLocationLocked() {
  184. // Prefer mLastLocationUpdate to LocationManager.getLastLocation().
  185. Location location = mReceivingLocationUpdates ? mLastLocationUpdate : null;
  186. if (location == null && !mFences.isEmpty()) {
  187. location = mLocationManager.getLastLocation();
  188. }
  189. // Early out for null location.
  190. if (location == null) {
  191. return null;
  192. }
  193. // Early out for stale location.
  194. long now = SystemClock.elapsedRealtimeNanos();
  195. if (now - location.getElapsedRealtimeNanos() > MAX_AGE_NANOS) {
  196. return null;
  197. }
  198. // Made it this far? Return our fresh, valid location.
  199. return location;
  200. }
  201. /**
  202. * The geofence update loop. This function removes expired fences, then tests the most
  203. * recently-received {@link Location} against each registered {@link GeofenceState}, sending
  204. * {@link Intent}s for geofences that have been tripped. It also adjusts the active location
  205. * update request with {@link LocationManager} as appropriate for any active geofences.
  206. */
  207. // Runs on the handler.
  208. private void updateFences() {
  209. List<PendingIntent> enterIntents = new LinkedList<PendingIntent>();
  210. List<PendingIntent> exitIntents = new LinkedList<PendingIntent>();
  211. synchronized (mLock) {
  212. mPendingUpdate = false;
  213. // Remove expired fences.
  214. removeExpiredFencesLocked();
  215. // Get a location to work with, either received via onLocationChanged() or
  216. // via LocationManager.getLastLocation().
  217. Location location = getFreshLocationLocked();
  218. // Update all fences.
  219. // Keep track of the distance to the nearest fence.
  220. double minFenceDistance = Double.MAX_VALUE;
  221. boolean needUpdates = false;
  222. for (GeofenceState state : mFences) {
  223. if (mBlacklist.isBlacklisted(state.mPackageName)) {
  224. if (D) {
  225. Slog.d(TAG, "skipping geofence processing for blacklisted app: "
  226. + state.mPackageName);
  227. }
  228. continue;
  229. }
  230. int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
  231. if (op >= 0) {
  232. if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
  233. state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
  234. if (D) {
  235. Slog.d(TAG, "skipping geofence processing for no op app: "
  236. + state.mPackageName);
  237. }
  238. continue;
  239. }
  240. }
  241. needUpdates = true;
  242. if (location != null) {
  243. int event = state.processLocation(location);
  244. if ((event & GeofenceState.FLAG_ENTER) != 0) {
  245. enterIntents.add(state.mIntent);
  246. }
  247. if ((event & GeofenceState.FLAG_EXIT) != 0) {
  248. exitIntents.add(state.mIntent);
  249. }
  250. // FIXME: Ideally this code should take into account the accuracy of the
  251. // location fix that was used to calculate the distance in the first place.
  252. double fenceDistance = state.getDistanceToBoundary(); // MAX_VALUE if unknown
  253. if (fenceDistance < minFenceDistance) {
  254. minFenceDistance = fenceDistance;
  255. }
  256. }
  257. }
  258. // Request or cancel location updates if needed.
  259. if (needUpdates) {
  260. // Request location updates.
  261. // Compute a location update interval based on the distance to the nearest fence.
  262. long intervalMs;
  263. if (location != null && Double.compare(minFenceDistance, Double.MAX_VALUE) != 0) {
  264. intervalMs = (long)Math.min(MAX_INTERVAL_MS, Math.max(MIN_INTERVAL_MS,
  265. minFenceDistance * 1000 / MAX_SPEED_M_S));
  266. } else {
  267. intervalMs = MIN_INTERVAL_MS;
  268. }
  269. if (!mReceivingLocationUpdates || mLocationUpdateInterval != intervalMs) {
  270. mReceivingLocationUpdates = true;
  271. mLocationUpdateInterval = intervalMs;
  272. mLastLocationUpdate = location;
  273. LocationRequest request = new LocationRequest();
  274. request.setInterval(intervalMs).setFastestInterval(0);
  275. mLocationManager.requestLocationUpdates(request, this, mHandler.getLooper());
  276. }
  277. } else {
  278. // Cancel location updates.
  279. if (mReceivingLocationUpdates) {
  280. mReceivingLocationUpdates = false;
  281. mLocationUpdateInterval = 0;
  282. mLastLocationUpdate = null;
  283. mLocationManager.removeUpdates(this);
  284. }
  285. }
  286. if (D) {
  287. Slog.d(TAG, "updateFences: location=" + location
  288. + ", mFences.size()=" + mFences.size()
  289. + ", mReceivingLocationUpdates=" + mReceivingLocationUpdates
  290. + ", mLocationUpdateInterval=" + mLocationUpdateInterval
  291. + ", mLastLocationUpdate=" + mLastLocationUpdate);
  292. }
  293. }
  294. // release lock before sending intents
  295. for (PendingIntent intent : exitIntents) {
  296. sendIntentExit(intent);
  297. }
  298. for (PendingIntent intent : enterIntents) {
  299. sendIntentEnter(intent);
  300. }
  301. }
  302. private void sendIntentEnter(PendingIntent pendingIntent) {
  303. if (D) {
  304. Slog.d(TAG, "sendIntentEnter: pendingIntent=" + pendingIntent);
  305. }
  306. Intent intent = new Intent();
  307. intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
  308. sendIntent(pendingIntent, intent);
  309. }
  310. private void sendIntentExit(PendingIntent pendingIntent) {
  311. if (D) {
  312. Slog.d(TAG, "sendIntentExit: pendingIntent=" + pendingIntent);
  313. }
  314. Intent intent = new Intent();
  315. intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
  316. sendIntent(pendingIntent, intent);
  317. }
  318. private void sendIntent(PendingIntent pendingIntent, Intent intent) {
  319. mWakeLock.acquire();
  320. try {
  321. pendingIntent.send(mContext, 0, intent, this, null,
  322. android.Manifest.permission.ACCESS_FINE_LOCATION);
  323. } catch (PendingIntent.CanceledException e) {
  324. removeFence(null, pendingIntent);
  325. mWakeLock.release();
  326. }
  327. // ...otherwise, mWakeLock.release() gets called by onSendFinished()
  328. }
  329. // Runs on the handler (which was passed into LocationManager.requestLocationUpdates())
  330. @Override
  331. public void onLocationChanged(Location location) {
  332. synchronized (mLock) {
  333. if (mReceivingLocationUpdates) {
  334. mLastLocationUpdate = location;
  335. }
  336. // Update the fences immediately before returning in
  337. // case the caller is holding a wakelock.
  338. if (mPendingUpdate) {
  339. mHandler.removeMessages(MSG_UPDATE_FENCES);
  340. } else {
  341. mPendingUpdate = true;
  342. }
  343. }
  344. updateFences();
  345. }
  346. @Override
  347. public void onStatusChanged(String provider, int status, Bundle extras) { }
  348. @Override
  349. public void onProviderEnabled(String provider) { }
  350. @Override
  351. public void onProviderDisabled(String provider) { }
  352. @Override
  353. public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
  354. String resultData, Bundle resultExtras) {
  355. mWakeLock.release();
  356. }
  357. public void dump(PrintWriter pw) {
  358. pw.println(" Geofences:");
  359. for (GeofenceState state : mFences) {
  360. pw.append(" ");
  361. pw.append(state.mPackageName);
  362. pw.append(" ");
  363. pw.append(state.mFence.toString());
  364. pw.append("\n");
  365. }
  366. }
  367. private final class GeofenceHandler extends Handler {
  368. public GeofenceHandler() {
  369. super(true /*async*/);
  370. }
  371. @Override
  372. public void handleMessage(Message msg) {
  373. switch (msg.what) {
  374. case MSG_UPDATE_FENCES: {
  375. updateFences();
  376. break;
  377. }
  378. }
  379. }
  380. }
  381. }