GeofenceManager.java revision 6fa9ad4afcd762aea519ff61811386c23d18ddb2
1/* 2 * Copyright (C) 20012 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 17package com.android.server.location; 18 19import java.io.PrintWriter; 20import java.util.Iterator; 21import java.util.LinkedList; 22import java.util.List; 23 24import android.Manifest.permission; 25import android.app.PendingIntent; 26import android.content.Context; 27import android.content.Intent; 28import android.location.Geofence; 29import android.location.Location; 30import android.location.LocationListener; 31import android.location.LocationManager; 32import android.location.LocationRequest; 33import android.os.Bundle; 34import android.os.Looper; 35import android.os.PowerManager; 36import android.os.SystemClock; 37 38public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { 39 private static final String TAG = "GeofenceManager"; 40 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 private final Context mContext; 49 private final LocationManager mLocationManager; 50 private final PowerManager.WakeLock mWakeLock; 51 private final Looper mLooper; // looper thread to take location updates on 52 53 private Object mLock = new Object(); 54 55 // access to members below is synchronized on mLock 56 private Location mLastLocation; 57 private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); 58 59 public GeofenceManager(Context context) { 60 mContext = context; 61 mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); 62 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 63 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 64 mLooper = Looper.myLooper(); 65 66 LocationRequest request = new LocationRequest() 67 .setQuality(LocationRequest.POWER_NONE) 68 .setFastestInterval(0); 69 mLocationManager.requestLocationUpdates(request, this, Looper.myLooper()); 70 } 71 72 public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid, 73 String packageName) { 74 GeofenceState state = new GeofenceState(geofence, mLastLocation, 75 request.getExpireAt(), packageName, intent); 76 77 synchronized (mLock) { 78 // first make sure it doesn't already exist 79 for (int i = mFences.size() - 1; i >= 0; i--) { 80 GeofenceState w = mFences.get(i); 81 if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) { 82 // already exists, remove the old one 83 mFences.remove(i); 84 break; 85 } 86 } 87 mFences.add(state); 88 updateProviderRequirementsLocked(); 89 } 90 } 91 92 public void removeFence(Geofence fence, PendingIntent intent) { 93 synchronized (mLock) { 94 Iterator<GeofenceState> iter = mFences.iterator(); 95 while (iter.hasNext()) { 96 GeofenceState state = iter.next(); 97 if (state.mIntent.equals(intent)) { 98 99 if (fence == null) { 100 // alwaus remove 101 iter.remove(); 102 } else { 103 // just remove matching fences 104 if (fence.equals(state.mFence)) { 105 iter.remove(); 106 } 107 } 108 } 109 } 110 updateProviderRequirementsLocked(); 111 } 112 } 113 114 public void removeFence(String packageName) { 115 synchronized (mLock) { 116 Iterator<GeofenceState> iter = mFences.iterator(); 117 while (iter.hasNext()) { 118 GeofenceState state = iter.next(); 119 if (state.mPackageName.equals(packageName)) { 120 iter.remove(); 121 } 122 } 123 updateProviderRequirementsLocked(); 124 } 125 } 126 127 private void removeExpiredFencesLocked() { 128 long time = SystemClock.elapsedRealtime(); 129 Iterator<GeofenceState> iter = mFences.iterator(); 130 while (iter.hasNext()) { 131 GeofenceState state = iter.next(); 132 if (state.mExpireAt < time) { 133 iter.remove(); 134 } 135 } 136 } 137 138 private void processLocation(Location location) { 139 List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); 140 List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); 141 142 synchronized (mLock) { 143 mLastLocation = location; 144 145 removeExpiredFencesLocked(); 146 147 for (GeofenceState state : mFences) { 148 int event = state.processLocation(location); 149 if ((event & GeofenceState.FLAG_ENTER) != 0) { 150 enterIntents.add(state.mIntent); 151 } 152 if ((event & GeofenceState.FLAG_EXIT) != 0) { 153 exitIntents.add(state.mIntent); 154 } 155 } 156 updateProviderRequirementsLocked(); 157 } 158 159 // release lock before sending intents 160 for (PendingIntent intent : exitIntents) { 161 sendIntentExit(intent); 162 } 163 for (PendingIntent intent : enterIntents) { 164 sendIntentEnter(intent); 165 } 166 } 167 168 private void sendIntentEnter(PendingIntent pendingIntent) { 169 Intent intent = new Intent(); 170 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); 171 sendIntent(pendingIntent, intent); 172 } 173 174 private void sendIntentExit(PendingIntent pendingIntent) { 175 Intent intent = new Intent(); 176 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); 177 sendIntent(pendingIntent, intent); 178 } 179 180 private void sendIntent(PendingIntent pendingIntent, Intent intent) { 181 try { 182 mWakeLock.acquire(); 183 pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); 184 } catch (PendingIntent.CanceledException e) { 185 removeFence(null, pendingIntent); 186 mWakeLock.release(); 187 } 188 } 189 190 private void updateProviderRequirementsLocked() { 191 double minDistance = Double.MAX_VALUE; 192 for (GeofenceState state : mFences) { 193 if (state.getDistance() < minDistance) { 194 minDistance = state.getDistance(); 195 } 196 } 197 198 if (minDistance == Double.MAX_VALUE) { 199 disableLocationLocked(); 200 } else { 201 int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; 202 requestLocationLocked(intervalMs); 203 } 204 } 205 206 private void requestLocationLocked(int intervalMs) { 207 mLocationManager.requestLocationUpdates(new LocationRequest().setInterval(intervalMs), this, 208 mLooper); 209 } 210 211 private void disableLocationLocked() { 212 mLocationManager.removeUpdates(this); 213 } 214 215 @Override 216 public void onLocationChanged(Location location) { 217 processLocation(location); 218 } 219 220 @Override 221 public void onStatusChanged(String provider, int status, Bundle extras) { } 222 223 @Override 224 public void onProviderEnabled(String provider) { } 225 226 @Override 227 public void onProviderDisabled(String provider) { } 228 229 @Override 230 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, 231 String resultData, Bundle resultExtras) { 232 mWakeLock.release(); 233 } 234 235 public void dump(PrintWriter pw) { 236 pw.println(" Geofences:"); 237 238 for (GeofenceState state : mFences) { 239 pw.append(" "); 240 pw.append(state.mPackageName); 241 pw.append(" "); 242 pw.append(state.mFence.toString()); 243 pw.append("\n"); 244 } 245 } 246} 247