GeofenceManager.java revision e0fd693c6098f59004f9e96ad75c058e26c337b0
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.Location; 29import android.location.LocationListener; 30import android.location.LocationManager; 31import android.os.Bundle; 32import android.os.Looper; 33import android.os.PowerManager; 34import android.os.SystemClock; 35 36public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { 37 static final String TAG = "GeofenceManager"; 38 39 /** 40 * Assume a maximum land speed, as a heuristic to throttle location updates. 41 * (Air travel should result in an airplane mode toggle which will 42 * force a new location update anyway). 43 */ 44 static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) 45 46 class GeofenceWrapper { 47 final Geofence fence; 48 final long expiry; 49 final String packageName; 50 final PendingIntent intent; 51 52 public GeofenceWrapper(Geofence fence, long expiry, String packageName, 53 PendingIntent intent) { 54 this.fence = fence; 55 this.expiry = expiry; 56 this.packageName = packageName; 57 this.intent = intent; 58 } 59 } 60 61 final Context mContext; 62 final LocationManager mLocationManager; 63 final PowerManager.WakeLock mWakeLock; 64 final Looper mLooper; // looper thread to take location updates on 65 66 // access to members below is synchronized on this 67 Location mLastLocation; 68 List<GeofenceWrapper> mFences = new LinkedList<GeofenceWrapper>(); 69 70 public GeofenceManager(Context context) { 71 mContext = context; 72 mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); 73 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 74 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 75 mLooper = Looper.myLooper(); 76 mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this); 77 } 78 79 public void addFence(double latitude, double longitude, float radius, long expiration, 80 PendingIntent intent, int uid, String packageName) { 81 long expiry = SystemClock.elapsedRealtime() + expiration; 82 if (expiration < 0) { 83 expiry = Long.MAX_VALUE; 84 } 85 Geofence fence = new Geofence(latitude, longitude, radius, mLastLocation); 86 GeofenceWrapper fenceWrapper = new GeofenceWrapper(fence, expiry, packageName, intent); 87 88 synchronized (this) { 89 mFences.add(fenceWrapper); 90 updateProviderRequirements(); 91 } 92 } 93 94 public void removeFence(PendingIntent intent) { 95 synchronized (this) { 96 Iterator<GeofenceWrapper> iter = mFences.iterator(); 97 while (iter.hasNext()) { 98 GeofenceWrapper fenceWrapper = iter.next(); 99 if (fenceWrapper.intent.equals(intent)) { 100 iter.remove(); 101 } 102 } 103 updateProviderRequirements(); 104 } 105 } 106 107 public void removeFence(String packageName) { 108 synchronized (this) { 109 Iterator<GeofenceWrapper> iter = mFences.iterator(); 110 while (iter.hasNext()) { 111 GeofenceWrapper fenceWrapper = iter.next(); 112 if (fenceWrapper.packageName.equals(packageName)) { 113 iter.remove(); 114 } 115 } 116 updateProviderRequirements(); 117 } 118 } 119 120 void removeExpiredFences() { 121 synchronized (this) { 122 long time = SystemClock.elapsedRealtime(); 123 Iterator<GeofenceWrapper> iter = mFences.iterator(); 124 while (iter.hasNext()) { 125 GeofenceWrapper fenceWrapper = iter.next(); 126 if (fenceWrapper.expiry < time) { 127 iter.remove(); 128 } 129 } 130 } 131 } 132 133 void processLocation(Location location) { 134 List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); 135 List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); 136 137 synchronized (this) { 138 mLastLocation = location; 139 140 removeExpiredFences(); 141 142 for (GeofenceWrapper fenceWrapper : mFences) { 143 int event = fenceWrapper.fence.processLocation(location); 144 if ((event & Geofence.FLAG_ENTER) != 0) { 145 enterIntents.add(fenceWrapper.intent); 146 } 147 if ((event & Geofence.FLAG_EXIT) != 0) { 148 exitIntents.add(fenceWrapper.intent); 149 } 150 } 151 updateProviderRequirements(); 152 } 153 154 // release lock before sending intents 155 for (PendingIntent intent : exitIntents) { 156 sendIntentExit(intent); 157 } 158 for (PendingIntent intent : enterIntents) { 159 sendIntentEnter(intent); 160 } 161 } 162 163 void sendIntentEnter(PendingIntent pendingIntent) { 164 Intent intent = new Intent(); 165 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); 166 sendIntent(pendingIntent, intent); 167 } 168 169 void sendIntentExit(PendingIntent pendingIntent) { 170 Intent intent = new Intent(); 171 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); 172 sendIntent(pendingIntent, intent); 173 } 174 175 void sendIntent(PendingIntent pendingIntent, Intent intent) { 176 try { 177 mWakeLock.acquire(); 178 pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); 179 } catch (PendingIntent.CanceledException e) { 180 removeFence(pendingIntent); 181 mWakeLock.release(); 182 } 183 } 184 185 void updateProviderRequirements() { 186 synchronized (this) { 187 double minDistance = Double.MAX_VALUE; 188 for (GeofenceWrapper alert : mFences) { 189 if (alert.fence.getDistance() < minDistance) { 190 minDistance = alert.fence.getDistance(); 191 } 192 } 193 194 if (minDistance == Double.MAX_VALUE) { 195 disableLocation(); 196 } else { 197 int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; 198 setLocationInterval(intervalMs); 199 } 200 } 201 } 202 203 void setLocationInterval(int intervalMs) { 204 mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, intervalMs, 0, this, 205 mLooper); 206 } 207 208 void disableLocation() { 209 mLocationManager.removeUpdates(this); 210 } 211 212 @Override 213 public void onLocationChanged(Location location) { 214 processLocation(location); 215 } 216 217 @Override 218 public void onStatusChanged(String provider, int status, Bundle extras) { } 219 220 @Override 221 public void onProviderEnabled(String provider) { } 222 223 @Override 224 public void onProviderDisabled(String provider) { } 225 226 @Override 227 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, 228 String resultData, Bundle resultExtras) { 229 mWakeLock.release(); 230 } 231 232 public void dump(PrintWriter pw) { 233 pw.println(" Geofences:"); 234 for (GeofenceWrapper fenceWrapper : mFences) { 235 pw.append(" "); 236 pw.append(fenceWrapper.packageName); 237 pw.append(" "); 238 pw.append(fenceWrapper.fence.toString()); 239 pw.append("\n"); 240 } 241 } 242} 243