diff options
Diffstat (limited to 'services/java/com/android/server/LocationManagerService.java')
-rw-r--r-- | services/java/com/android/server/LocationManagerService.java | 1926 |
1 files changed, 807 insertions, 1119 deletions
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 198ba8b..a6c3860 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,21 +16,21 @@ package com.android.server; -import android.app.Activity; import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.Cursor; import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; +import android.location.Geofence; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; import android.location.ILocationListener; @@ -39,26 +39,28 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; +import android.location.LocationRequest; import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.NameValueTable; import android.util.Log; import android.util.Slog; -import android.util.PrintWriterPrinter; import com.android.internal.content.PackageMonitor; - +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceManager; import com.android.server.location.GpsLocationProvider; @@ -70,8 +72,7 @@ import com.android.server.location.PassiveProvider; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -83,135 +84,273 @@ import java.util.Set; /** * The service class that manages LocationProviders and issues location * updates and alerts. - * - * {@hide} */ -public class LocationManagerService extends ILocationManager.Stub implements Runnable { +public class LocationManagerService extends ILocationManager.Stub implements Observer, Runnable { private static final String TAG = "LocationManagerService"; - private static final boolean LOCAL_LOGV = false; + public static final boolean D = false; + + private static final String WAKELOCK_KEY = TAG; + private static final String THREAD_NAME = TAG; private static final String ACCESS_FINE_LOCATION = - android.Manifest.permission.ACCESS_FINE_LOCATION; + android.Manifest.permission.ACCESS_FINE_LOCATION; private static final String ACCESS_COARSE_LOCATION = - android.Manifest.permission.ACCESS_COARSE_LOCATION; + android.Manifest.permission.ACCESS_COARSE_LOCATION; private static final String ACCESS_MOCK_LOCATION = - android.Manifest.permission.ACCESS_MOCK_LOCATION; + android.Manifest.permission.ACCESS_MOCK_LOCATION; private static final String ACCESS_LOCATION_EXTRA_COMMANDS = - android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; + android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; private static final String INSTALL_LOCATION_PROVIDER = - android.Manifest.permission.INSTALL_LOCATION_PROVIDER; + android.Manifest.permission.INSTALL_LOCATION_PROVIDER; + + private static final String NETWORK_LOCATION_SERVICE_ACTION = + "com.android.location.service.v2.NetworkLocationProvider"; + private static final String FUSED_LOCATION_SERVICE_ACTION = + "com.android.location.service.FusedLocationProvider"; + + private static final int MSG_LOCATION_CHANGED = 1; + + // Accuracy in meters above which a location is considered coarse + private static final double COARSE_ACCURACY_M = 100.0; + private static final String EXTRA_COARSE_LOCATION = "coarseLocation"; + + private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111000; + + /** + * Maximum latitude of 1 meter from the pole. + * This keeps cosine(MAX_LATITUDE) to a non-zero value; + */ + private static final double MAX_LATITUDE = + 90.0 - (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR); // Location Providers may sometimes deliver location updates // slightly faster that requested - provide grace period so // we don't unnecessarily filter events that are otherwise on // time - private static final int MAX_PROVIDER_SCHEDULING_JITTER = 100; + private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100; - // Set of providers that are explicitly enabled - private final Set<String> mEnabledProviders = new HashSet<String>(); + private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest(); - // Set of providers that are explicitly disabled - private final Set<String> mDisabledProviders = new HashSet<String>(); - - // Locations, status values, and extras for mock providers - private final HashMap<String,MockProvider> mMockProviders = new HashMap<String,MockProvider>(); + private final Context mContext; - private static boolean sProvidersLoaded = false; + // used internally for synchronization + private final Object mLock = new Object(); - private final Context mContext; - private PackageManager mPackageManager; // final after initialize() - private String mNetworkLocationProviderPackageName; // only used on handler thread - private String mGeocodeProviderPackageName; // only used on handler thread + // --- fields below are final after init() --- + private GeofenceManager mGeofenceManager; + private PowerManager.WakeLock mWakeLock; + private PackageManager mPackageManager; private GeocoderProxy mGeocodeProvider; private IGpsStatusProvider mGpsStatusProvider; private INetInitiatedListener mNetInitiatedListener; private LocationWorkerHandler mLocationHandler; + // track the passive provider for some special cases + private PassiveProvider mPassiveProvider; - // Cache the real providers for use in addTestProvider() and removeTestProvider() - LocationProviderProxy mNetworkLocationProvider; - LocationProviderInterface mGpsLocationProvider; + // --- fields below are protected by mWakeLock --- + private int mPendingBroadcasts; - // Handler messages - private static final int MESSAGE_LOCATION_CHANGED = 1; - private static final int MESSAGE_PACKAGE_UPDATED = 2; + // --- fields below are protected by mLock --- + // Set of providers that are explicitly enabled + private final Set<String> mEnabledProviders = new HashSet<String>(); - // wakelock variables - private final static String WAKELOCK_KEY = "LocationManagerService"; - private PowerManager.WakeLock mWakeLock = null; - private int mPendingBroadcasts; + // Set of providers that are explicitly disabled + private final Set<String> mDisabledProviders = new HashSet<String>(); - /** - * List of all receivers. - */ - private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>(); + // Mock (test) providers + private final HashMap<String, MockProvider> mMockProviders = + new HashMap<String, MockProvider>(); + // all receivers + private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>(); - /** - * List of location providers. - */ + // currently installed providers (with mocks replacing real providers) private final ArrayList<LocationProviderInterface> mProviders = - new ArrayList<LocationProviderInterface>(); - private final HashMap<String, LocationProviderInterface> mProvidersByName - = new HashMap<String, LocationProviderInterface>(); + new ArrayList<LocationProviderInterface>(); - /** - * Object used internally for synchronization - */ - private final Object mLock = new Object(); + // real providers, saved here when mocked out + private final HashMap<String, LocationProviderInterface> mRealProviders = + new HashMap<String, LocationProviderInterface>(); - /** - * Mapping from provider name to all its UpdateRecords - */ - private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider = - new HashMap<String,ArrayList<UpdateRecord>>(); + // mapping from provider name to provider + private final HashMap<String, LocationProviderInterface> mProvidersByName = + new HashMap<String, LocationProviderInterface>(); - /** - * Temporary filled in when computing min time for a provider. Access is - * protected by global lock mLock. - */ - private final WorkSource mTmpWorkSource = new WorkSource(); + // mapping from provider name to all its UpdateRecords + private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider = + new HashMap<String, ArrayList<UpdateRecord>>(); + + // mapping from provider name to last known location + private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>(); + + // all providers that operate over proxy, for authorizing incoming location + private final ArrayList<LocationProviderProxy> mProxyProviders = + new ArrayList<LocationProviderProxy>(); + + public LocationManagerService(Context context) { + super(); + mContext = context; + + if (D) Log.d(TAG, "Constructed"); - GeofenceManager mGeofenceManager; + // most startup is deferred until systemReady() + } - // Last known location for each provider - private HashMap<String,Location> mLastKnownLocation = - new HashMap<String,Location>(); + public void systemReady() { + Thread thread = new Thread(null, this, THREAD_NAME); + thread.start(); + } - private int mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Looper.prepare(); + mLocationHandler = new LocationWorkerHandler(); + init(); + Looper.loop(); + } - // for Settings change notification - private ContentQueryMap mSettings; + private void init() { + if (D) Log.d(TAG, "init()"); + + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mPackageManager = mContext.getPackageManager(); + + synchronized (mLock) { + loadProvidersLocked(); + } + mGeofenceManager = new GeofenceManager(mContext); + + // Register for Network (Wifi or Mobile) updates + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + + // listen for settings changes + ContentResolver resolver = mContext.getContentResolver(); + Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null, + "(" + NameValueTable.NAME + "=?)", + new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, null); + ContentQueryMap query = new ContentQueryMap(settingsCursor, NameValueTable.NAME, true, + mLocationHandler); + settingsCursor.close(); + query.addObserver(this); + mPackageMonitor.register(mContext, Looper.myLooper(), true); + } + + private void loadProvidersLocked() { + if (GpsLocationProvider.isSupported()) { + // Create a gps location provider + GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); + mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); + mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); + addProviderLocked(gpsProvider); + mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider); + } + + // create a passive location provider, which is always enabled + PassiveProvider passiveProvider = new PassiveProvider(this); + addProviderLocked(passiveProvider); + mEnabledProviders.add(passiveProvider.getName()); + mPassiveProvider = passiveProvider; + + /* + Load package name(s) containing location provider support. + These packages can contain services implementing location providers: + Geocoder Provider, Network Location Provider, and + Fused Location Provider. They will each be searched for + service components implementing these providers. + The location framework also has support for installation + of new location providers at run-time. The new package does not + have to be explicitly listed here, however it must have a signature + that matches the signature of at least one package on this list. + */ + Resources resources = mContext.getResources(); + ArrayList<String> providerPackageNames = new ArrayList<String>(); + String[] pkgs1 = resources.getStringArray( + com.android.internal.R.array.config_locationProviderPackageNames); + String[] pkgs2 = resources.getStringArray( + com.android.internal.R.array.config_overlay_locationProviderPackageNames); + if (D) Log.d(TAG, "built-in location providers: " + Arrays.toString(pkgs1)); + if (D) Log.d(TAG, "overlay location providers: " + Arrays.toString(pkgs2)); + if (pkgs1 != null) providerPackageNames.addAll(Arrays.asList(pkgs1)); + if (pkgs2 != null) providerPackageNames.addAll(Arrays.asList(pkgs2)); + + // bind to network provider + LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( + mContext, + LocationManager.NETWORK_PROVIDER, + NETWORK_LOCATION_SERVICE_ACTION, + providerPackageNames, mLocationHandler); + if (networkProvider != null) { + mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider); + mProxyProviders.add(networkProvider); + addProviderLocked(networkProvider); + } else { + Slog.w(TAG, "no network location provider found"); + } + + // bind to fused provider + LocationProviderProxy fusedLocationProvider = LocationProviderProxy.createAndBind( + mContext, + LocationManager.FUSED_PROVIDER, + FUSED_LOCATION_SERVICE_ACTION, + providerPackageNames, mLocationHandler); + if (fusedLocationProvider != null) { + addProviderLocked(fusedLocationProvider); + mProxyProviders.add(fusedLocationProvider); + mEnabledProviders.add(fusedLocationProvider.getName()); + } else { + Slog.e(TAG, "no fused location provider found", + new IllegalStateException("Location service needs a fused location provider")); + } + + // bind to geocoder provider + mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames); + if (mGeocodeProvider == null) { + Slog.e(TAG, "no geocoder provider found"); + } + + updateProvidersLocked(); + } /** * A wrapper class holding either an ILocationListener or a PendingIntent to receive * location updates. */ private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished { + final int mUid; // uid of receiver + final int mPid; // pid of receiver + final String mPackageName; // package name of receiver + final String mPermission; // best permission that receiver has + final ILocationListener mListener; final PendingIntent mPendingIntent; final Object mKey; + final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>(); int mPendingBroadcasts; - String mRequiredPermissions; - Receiver(ILocationListener listener) { + Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, + String packageName) { mListener = listener; - mPendingIntent = null; - mKey = listener.asBinder(); - } - - Receiver(PendingIntent intent) { mPendingIntent = intent; - mListener = null; - mKey = intent; + if (listener != null) { + mKey = listener.asBinder(); + } else { + mKey = intent; + } + mPermission = checkPermission(); + mUid = uid; + mPid = pid; + mPackageName = packageName; } @Override public boolean equals(Object otherObj) { if (otherObj instanceof Receiver) { - return mKey.equals( - ((Receiver)otherObj).mKey); + return mKey.equals(((Receiver)otherObj).mKey); } return false; } @@ -223,18 +362,19 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public String toString() { - String result; + StringBuilder s = new StringBuilder(); + s.append("Reciever["); + s.append(Integer.toHexString(System.identityHashCode(this))); if (mListener != null) { - result = "Receiver{" - + Integer.toHexString(System.identityHashCode(this)) - + " Listener " + mKey + "}"; + s.append(" listener"); } else { - result = "Receiver{" - + Integer.toHexString(System.identityHashCode(this)) - + " Intent " + mKey + "}"; + s.append(" intent"); } - result += "mUpdateRecords: " + mUpdateRecords; - return result; + for (String p : mUpdateRecords.keySet()) { + s.append(" ").append(mUpdateRecords.get(p).toString()); + } + s.append("]"); + return s.toString(); } public boolean isListener() { @@ -275,7 +415,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -309,7 +449,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -347,7 +487,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -361,9 +501,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public void binderDied() { - if (LOCAL_LOGV) { - Slog.v(TAG, "Location listener died"); - } + if (D) Log.d(TAG, "Location listener died"); + synchronized (mLock) { removeUpdatesLocked(this); } @@ -416,200 +555,25 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private final class SettingsObserver implements Observer { - @Override - public void update(Observable o, Object arg) { - synchronized (mLock) { - updateProvidersLocked(); - } + /** Settings Observer callback */ + @Override + public void update(Observable o, Object arg) { + synchronized (mLock) { + updateProvidersLocked(); } } - private void addProvider(LocationProviderInterface provider) { + private void addProviderLocked(LocationProviderInterface provider) { mProviders.add(provider); mProvidersByName.put(provider.getName(), provider); } - private void removeProvider(LocationProviderInterface provider) { + private void removeProviderLocked(LocationProviderInterface provider) { + provider.disable(); mProviders.remove(provider); mProvidersByName.remove(provider.getName()); } - private void loadProviders() { - synchronized (mLock) { - if (sProvidersLoaded) { - return; - } - - // Load providers - loadProvidersLocked(); - sProvidersLoaded = true; - } - } - - private void loadProvidersLocked() { - try { - _loadProvidersLocked(); - } catch (Exception e) { - Slog.e(TAG, "Exception loading providers:", e); - } - } - - private void _loadProvidersLocked() { - // Attempt to load "real" providers first - if (GpsLocationProvider.isSupported()) { - // Create a gps location provider - GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); - mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); - mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); - addProvider(gpsProvider); - mGpsLocationProvider = gpsProvider; - } - - // create a passive location provider, which is always enabled - PassiveProvider passiveProvider = new PassiveProvider(this); - addProvider(passiveProvider); - mEnabledProviders.add(passiveProvider.getName()); - - // initialize external network location and geocoder services. - // The initial value of mNetworkLocationProviderPackageName and - // mGeocodeProviderPackageName is just used to determine what - // signatures future mNetworkLocationProviderPackageName and - // mGeocodeProviderPackageName packages must have. So alternate - // providers can be installed under a different package name - // so long as they have the same signature as the original - // provider packages. - if (mNetworkLocationProviderPackageName != null) { - String packageName = findBestPackage(LocationProviderProxy.SERVICE_ACTION, - mNetworkLocationProviderPackageName); - if (packageName != null) { - mNetworkLocationProvider = new LocationProviderProxy(mContext, - LocationManager.NETWORK_PROVIDER, - packageName, mLocationHandler); - mNetworkLocationProviderPackageName = packageName; - addProvider(mNetworkLocationProvider); - } - } - if (mGeocodeProviderPackageName != null) { - String packageName = findBestPackage(GeocoderProxy.SERVICE_ACTION, - mGeocodeProviderPackageName); - if (packageName != null) { - mGeocodeProvider = new GeocoderProxy(mContext, packageName); - mGeocodeProviderPackageName = packageName; - } - } - - updateProvidersLocked(); - } - - /** - * Pick the best (network location provider or geocode provider) package. - * The best package: - * - implements serviceIntentName - * - has signatures that match that of sigPackageName - * - has the highest version value in a meta-data field in the service component - */ - String findBestPackage(String serviceIntentName, String sigPackageName) { - Intent intent = new Intent(serviceIntentName); - List<ResolveInfo> infos = mPackageManager.queryIntentServices(intent, - PackageManager.GET_META_DATA); - if (infos == null) return null; - - int bestVersion = Integer.MIN_VALUE; - String bestPackage = null; - for (ResolveInfo info : infos) { - String packageName = info.serviceInfo.packageName; - // check signature - if (mPackageManager.checkSignatures(packageName, sigPackageName) != - PackageManager.SIGNATURE_MATCH) { - Slog.w(TAG, packageName + " implements " + serviceIntentName + - " but its signatures don't match those in " + sigPackageName + - ", ignoring"); - continue; - } - // read version - int version = 0; - if (info.serviceInfo.metaData != null) { - version = info.serviceInfo.metaData.getInt("version", 0); - } - if (LOCAL_LOGV) Slog.v(TAG, packageName + " implements " + serviceIntentName + - " with version " + version); - if (version > bestVersion) { - bestVersion = version; - bestPackage = packageName; - } - } - - return bestPackage; - } - - /** - * @param context the context that the LocationManagerService runs in - */ - public LocationManagerService(Context context) { - super(); - mContext = context; - Resources resources = context.getResources(); - - mNetworkLocationProviderPackageName = resources.getString( - com.android.internal.R.string.config_networkLocationProviderPackageName); - mGeocodeProviderPackageName = resources.getString( - com.android.internal.R.string.config_geocodeProviderPackageName); - - mPackageMonitor.register(context, null, true); - - if (LOCAL_LOGV) { - Slog.v(TAG, "Constructed LocationManager Service"); - } - } - - void systemReady() { - // we defer starting up the service until the system is ready - Thread thread = new Thread(null, this, "LocationManagerService"); - thread.start(); - } - - private void initialize() { - // Create a wake lock, needs to be done before calling loadProviders() below - PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mPackageManager = mContext.getPackageManager(); - - // Load providers - loadProviders(); - - // Register for Network (Wifi or Mobile) updates - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - // Register for Package Manager updates - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - intentFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); - mContext.registerReceiver(mBroadcastReceiver, intentFilter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mBroadcastReceiver, sdFilter); - - // listen for settings changes - ContentResolver resolver = mContext.getContentResolver(); - Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null, - "(" + NameValueTable.NAME + "=?)", - new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, - null); - mSettings = new ContentQueryMap(settingsCursor, NameValueTable.NAME, true, mLocationHandler); - SettingsObserver settingsObserver = new SettingsObserver(); - mSettings.addObserver(settingsObserver); - } - - @Override - public void run() - { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - Looper.prepare(); - mLocationHandler = new LocationWorkerHandler(); - initialize(); - mGeofenceManager = new GeofenceManager(mContext); - Looper.loop(); - } private boolean isAllowedBySettingsLocked(String provider) { if (mEnabledProviders.contains(provider)) { @@ -624,324 +588,131 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return Settings.Secure.isLocationProviderEnabled(resolver, provider); } - private String checkPermissionsSafe(String provider, String lastPermission) { - if (LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) { - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION permission"); - } - return ACCESS_FINE_LOCATION; - } - - // Assume any other provider requires the coarse or fine permission. - if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { - return ACCESS_FINE_LOCATION.equals(lastPermission) - ? lastPermission : ACCESS_COARSE_LOCATION; - } - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { + /** + * Throw SecurityException if caller has neither COARSE or FINE. + * Otherwise, return the best permission. + */ + private String checkPermission() { + if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) == + PackageManager.PERMISSION_GRANTED) { return ACCESS_FINE_LOCATION; + } else if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == + PackageManager.PERMISSION_GRANTED) { + return ACCESS_COARSE_LOCATION; } - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); - } - - private boolean isAllowedProviderSafe(String provider) { - if ((LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - return false; - } - if (LocationManager.NETWORK_PROVIDER.equals(provider) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) - && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - return false; - } - - return true; + throw new SecurityException("Location requires either ACCESS_COARSE_LOCATION or" + + "ACCESS_FINE_LOCATION permission"); } + /** + * Returns all providers by name, including passive, but excluding + * fused. + */ @Override public List<String> getAllProviders() { - try { - synchronized (mLock) { - return _getAllProvidersLocked(); + checkPermission(); + + ArrayList<String> out; + synchronized (mLock) { + out = new ArrayList<String>(mProviders.size()); + for (LocationProviderInterface provider : mProviders) { + String name = provider.getName(); + if (LocationManager.FUSED_PROVIDER.equals(name)) { + continue; + } + out.add(name); } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getAllProviders got exception:", e); - return null; } - } - private List<String> _getAllProvidersLocked() { - if (LOCAL_LOGV) { - Slog.v(TAG, "getAllProviders"); - } - ArrayList<String> out = new ArrayList<String>(mProviders.size()); - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - out.add(p.getName()); - } + if (D) Log.d(TAG, "getAllProviders()=" + out); return out; } + /** + * Return all providers by name, that match criteria and are optionally + * enabled. + * Can return passive provider, but never returns fused provider. + */ @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { - try { - synchronized (mLock) { - return _getProvidersLocked(criteria, enabledOnly); - } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getProviders got exception:", e); - return null; - } - } + checkPermission(); - private List<String> _getProvidersLocked(Criteria criteria, boolean enabledOnly) { - if (LOCAL_LOGV) { - Slog.v(TAG, "getProviders"); - } - ArrayList<String> out = new ArrayList<String>(mProviders.size()); - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - String name = p.getName(); - if (isAllowedProviderSafe(name)) { + ArrayList<String> out; + synchronized (mLock) { + out = new ArrayList<String>(mProviders.size()); + for (LocationProviderInterface provider : mProviders) { + String name = provider.getName(); + if (LocationManager.FUSED_PROVIDER.equals(name)) { + continue; + } if (enabledOnly && !isAllowedBySettingsLocked(name)) { continue; } - if (criteria != null && !p.meetsCriteria(criteria)) { + if (criteria != null && !LocationProvider.propertiesMeetCriteria( + name, provider.getProperties(), criteria)) { continue; } out.add(name); } } - return out; - } - /** - * Returns the next looser power requirement, in the sequence: - * - * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT - */ - private int nextPower(int power) { - switch (power) { - case Criteria.POWER_LOW: - return Criteria.POWER_MEDIUM; - case Criteria.POWER_MEDIUM: - return Criteria.POWER_HIGH; - case Criteria.POWER_HIGH: - return Criteria.NO_REQUIREMENT; - case Criteria.NO_REQUIREMENT: - default: - return Criteria.NO_REQUIREMENT; - } - } - - /** - * Returns the next looser accuracy requirement, in the sequence: - * - * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT - */ - private int nextAccuracy(int accuracy) { - if (accuracy == Criteria.ACCURACY_FINE) { - return Criteria.ACCURACY_COARSE; - } else { - return Criteria.NO_REQUIREMENT; - } - } - - private class LpPowerComparator implements Comparator<LocationProviderInterface> { - @Override - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - // Smaller is better - return (l1.getPowerRequirement() - l2.getPowerRequirement()); - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (l1.getPowerRequirement() == l2.getPowerRequirement()); - } - } - - private class LpAccuracyComparator implements Comparator<LocationProviderInterface> { - @Override - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - // Smaller is better - return (l1.getAccuracy() - l2.getAccuracy()); - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (l1.getAccuracy() == l2.getAccuracy()); - } - } - - private class LpCapabilityComparator implements Comparator<LocationProviderInterface> { - - private static final int ALTITUDE_SCORE = 4; - private static final int BEARING_SCORE = 4; - private static final int SPEED_SCORE = 4; - - private int score(LocationProviderInterface p) { - return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + - (p.supportsBearing() ? BEARING_SCORE : 0) + - (p.supportsSpeed() ? SPEED_SCORE : 0); - } - - @Override - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - return (score(l2) - score(l1)); // Bigger is better - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (score(l1) == score(l2)); - } - } - - private LocationProviderInterface best(List<String> providerNames) { - ArrayList<LocationProviderInterface> providers; - synchronized (mLock) { - providers = new ArrayList<LocationProviderInterface>(providerNames.size()); - for (String name : providerNames) { - providers.add(mProvidersByName.get(name)); - } - } - - if (providers.size() < 2) { - return providers.get(0); - } - - // First, sort by power requirement - Collections.sort(providers, new LpPowerComparator()); - int power = providers.get(0).getPowerRequirement(); - if (power < providers.get(1).getPowerRequirement()) { - return providers.get(0); - } - - int idx, size; - - ArrayList<LocationProviderInterface> tmp = new ArrayList<LocationProviderInterface>(); - idx = 0; - size = providers.size(); - while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) { - tmp.add(providers.get(idx)); - idx++; - } - - // Next, sort by accuracy - Collections.sort(tmp, new LpAccuracyComparator()); - int acc = tmp.get(0).getAccuracy(); - if (acc < tmp.get(1).getAccuracy()) { - return tmp.get(0); - } - - ArrayList<LocationProviderInterface> tmp2 = new ArrayList<LocationProviderInterface>(); - idx = 0; - size = tmp.size(); - while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) { - tmp2.add(tmp.get(idx)); - idx++; - } - - // Finally, sort by capability "score" - Collections.sort(tmp2, new LpCapabilityComparator()); - return tmp2.get(0); + if (D) Log.d(TAG, "getProviders()=" + out); + return out; } /** - * Returns the name of the provider that best meets the given criteria. Only providers - * that are permitted to be accessed by the calling activity will be - * returned. If several providers meet the criteria, the one with the best - * accuracy is returned. If no provider meets the criteria, - * the criteria are loosened in the following sequence: - * - * <ul> - * <li> power requirement - * <li> accuracy - * <li> bearing - * <li> speed - * <li> altitude - * </ul> - * - * <p> Note that the requirement on monetary cost is not removed - * in this process. - * - * @param criteria the criteria that need to be matched - * @param enabledOnly if true then only a provider that is currently enabled is returned - * @return name of the provider that best matches the requirements + * Return the name of the best provider given a Criteria object. + * This method has been deprecated from the public API, + * and the whole LoactionProvider (including #meetsCriteria) + * has been deprecated as well. So this method now uses + * some simplified logic. */ @Override public String getBestProvider(Criteria criteria, boolean enabledOnly) { - List<String> goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } + String result = null; + checkPermission(); - // Make a copy of the criteria that we can modify - criteria = new Criteria(criteria); - - // Loosen power requirement - int power = criteria.getPowerRequirement(); - while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { - power = nextPower(power); - criteria.setPowerRequirement(power); - goodProviders = getProviders(criteria, enabledOnly); - } - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Loosen accuracy requirement - int accuracy = criteria.getAccuracy(); - while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { - accuracy = nextAccuracy(accuracy); - criteria.setAccuracy(accuracy); - goodProviders = getProviders(criteria, enabledOnly); - } - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + List<String> providers = getProviders(criteria, enabledOnly); + if (providers.size() < 1) { + result = pickBest(providers); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return result; } - - // Remove bearing requirement - criteria.setBearingRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + providers = getProviders(null, enabledOnly); + if (providers.size() >= 1) { + result = pickBest(providers); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return result; } - // Remove speed requirement - criteria.setSpeedRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return null; + } - // Remove altitude requirement - criteria.setAltitudeRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + private String pickBest(List<String> providers) { + if (providers.contains(LocationManager.NETWORK_PROVIDER)) { + return LocationManager.NETWORK_PROVIDER; + } else if (providers.contains(LocationManager.GPS_PROVIDER)) { + return LocationManager.GPS_PROVIDER; + } else { + return providers.get(0); } - - return null; } @Override public boolean providerMeetsCriteria(String provider, Criteria criteria) { + checkPermission(); + LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { throw new IllegalArgumentException("provider=" + provider); } - return p.meetsCriteria(criteria); + + boolean result = LocationProvider.propertiesMeetCriteria( + p.getName(), p.getProperties(), criteria); + if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result); + return result; } private void updateProvidersLocked() { @@ -968,16 +739,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run int listeners = 0; LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return; - } + if (p == null) return; ArrayList<Receiver> deadReceivers = null; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); if (records != null) { final int N = records.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { UpdateRecord record = records.get(i); // Sends a notification message to the receiver if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) { @@ -991,7 +760,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } if (deadReceivers != null) { - for (int i=deadReceivers.size()-1; i>=0; i--) { + for (int i = deadReceivers.size() - 1; i >= 0; i--) { removeUpdatesLocked(deadReceivers.get(i)); } } @@ -999,59 +768,70 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabled) { p.enable(); if (listeners > 0) { - p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); - p.enableLocationTracking(true); + applyRequirementsLocked(provider); } } else { - p.enableLocationTracking(false); p.disable(); } } - private long getMinTimeLocked(String provider) { - long minTime = Long.MAX_VALUE; + private void applyRequirementsLocked(String provider) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) return; + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - mTmpWorkSource.clear(); + WorkSource worksource = new WorkSource(); + ProviderRequest providerRequest = new ProviderRequest(); + if (records != null) { - for (int i=records.size()-1; i>=0; i--) { - UpdateRecord ur = records.get(i); - long curTime = ur.mMinTime; - if (curTime < minTime) { - minTime = curTime; + for (UpdateRecord record : records) { + LocationRequest locationRequest = record.mRequest; + + if (providerRequest.locationRequests == null) { + providerRequest.locationRequests = new ArrayList<LocationRequest>(); + } + + providerRequest.locationRequests.add(locationRequest); + if (locationRequest.getInterval() < providerRequest.interval) { + providerRequest.reportLocation = true; + providerRequest.interval = locationRequest.getInterval(); } } - long inclTime = (minTime*3)/2; - for (int i=records.size()-1; i>=0; i--) { - UpdateRecord ur = records.get(i); - if (ur.mMinTime <= inclTime) { - mTmpWorkSource.add(ur.mUid); + + if (providerRequest.reportLocation) { + // calculate who to blame for power + // This is somewhat arbitrary. We pick a threshold interval + // that is slightly higher that the minimum interval, and + // spread the blame across all applications with a request + // under that threshold. + long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + for (UpdateRecord record : records) { + LocationRequest locationRequest = record.mRequest; + if (locationRequest.getInterval() <= thresholdInterval) { + worksource.add(record.mReceiver.mUid); + } } } } - return minTime; + + if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest); + p.setRequest(providerRequest, worksource); } private class UpdateRecord { final String mProvider; + final LocationRequest mRequest; final Receiver mReceiver; - final long mMinTime; - final float mMinDistance; - final boolean mSingleShot; - final int mUid; Location mLastFixBroadcast; long mLastStatusBroadcast; /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, long minTime, float minDistance, boolean singleShot, - Receiver receiver, int uid) { + UpdateRecord(String provider, LocationRequest request, Receiver receiver) { mProvider = provider; + mRequest = request; mReceiver = receiver; - mMinTime = minTime; - mMinDistance = minDistance; - mSingleShot = singleShot; - mUid = uid; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); if (records == null) { @@ -1067,45 +847,49 @@ public class LocationManagerService extends ILocationManager.Stub implements Run * Method to be called when a record will no longer be used. Calling this multiple times * must have the same effect as calling it once. */ - void disposeLocked() { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(this.mProvider); - if (records != null) { - records.remove(this); + void disposeLocked(boolean removeReceiver) { + // remove from mRecordsByProvider + ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider); + if (globalRecords != null) { + globalRecords.remove(this); + } + + if (!removeReceiver) return; // the caller will handle the rest + + // remove from Receiver#mUpdateRecords + HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords; + if (receiverRecords != null) { + receiverRecords.remove(this.mProvider); + + // and also remove the Receiver if it has no more update records + if (removeReceiver && receiverRecords.size() == 0) { + removeUpdatesLocked(mReceiver); + } } } @Override public String toString() { - return "UpdateRecord{" - + Integer.toHexString(System.identityHashCode(this)) - + " mProvider: " + mProvider + " mUid: " + mUid + "}"; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver); - pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance); - pw.println(prefix + "mSingleShot=" + mSingleShot); - pw.println(prefix + "mUid=" + mUid); - pw.println(prefix + "mLastFixBroadcast:"); - if (mLastFixBroadcast != null) { - mLastFixBroadcast.dump(new PrintWriterPrinter(pw), prefix + " "); - } - pw.println(prefix + "mLastStatusBroadcast=" + mLastStatusBroadcast); + StringBuilder s = new StringBuilder(); + s.append("UpdateRecord["); + s.append(mProvider); + s.append(' ').append(mReceiver.mPackageName).append('('); + s.append(mReceiver.mUid).append(')'); + s.append(' ').append(mRequest); + s.append(']'); + return s.toString(); } } - private Receiver getReceiver(ILocationListener listener) { + private Receiver getReceiver(ILocationListener listener, int pid, int uid, String packageName) { IBinder binder = listener.asBinder(); Receiver receiver = mReceivers.get(binder); if (receiver == null) { - receiver = new Receiver(listener); + receiver = new Receiver(listener, null, pid, uid, packageName); mReceivers.put(binder, receiver); try { - if (receiver.isListener()) { - receiver.getListener().asBinder().linkToDeath(receiver, 0); - } + receiver.getListener().asBinder().linkToDeath(receiver, 0); } catch (RemoteException e) { Slog.e(TAG, "linkToDeath failed:", e); return null; @@ -1114,58 +898,29 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return receiver; } - private Receiver getReceiver(PendingIntent intent) { + private Receiver getReceiver(PendingIntent intent, int pid, int uid, String packageName) { Receiver receiver = mReceivers.get(intent); if (receiver == null) { - receiver = new Receiver(intent); + receiver = new Receiver(null, intent, pid, uid, packageName); mReceivers.put(intent, receiver); } return receiver; } - private boolean providerHasListener(String provider, int uid, Receiver excludedReceiver) { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - if (records != null) { - for (int i = records.size() - 1; i >= 0; i--) { - UpdateRecord record = records.get(i); - if (record.mUid == uid && record.mReceiver != excludedReceiver) { - return true; - } - } - } - return false; - } + private String checkPermissionAndRequest(LocationRequest request) { + String perm = checkPermission(); - @Override - public void requestLocationUpdates(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, ILocationListener listener) { - if (criteria != null) { - // FIXME - should we consider using multiple providers simultaneously - // rather than only the best one? - // Should we do anything different for single shot fixes? - provider = getBestProvider(criteria, true); - if (provider == null) { - throw new IllegalArgumentException("no providers found for criteria"); - } - } - try { - synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, - getReceiver(listener)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "requestUpdates got exception:", e); + if (ACCESS_COARSE_LOCATION.equals(perm)) { + request.applyCoarsePermissionRestrictions(); } + return perm; } - void validatePackageName(int uid, String packageName) { + private void checkPackageName(String packageName) { if (packageName == null) { - throw new SecurityException("packageName cannot be null"); + throw new SecurityException("invalid package name: " + packageName); } + int uid = Binder.getCallingUid(); String[] packages = mPackageManager.getPackagesForUid(uid); if (packages == null) { throw new SecurityException("invalid UID " + uid); @@ -1173,202 +928,188 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (String pkg : packages) { if (packageName.equals(pkg)) return; } - throw new SecurityException("invalid package name"); + throw new SecurityException("invalid package name: " + packageName); } - void validatePendingIntent(PendingIntent intent) { - if (intent.isTargetedToPackage()) { - return; + private void checkPendingIntent(PendingIntent intent) { + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + intent); } - Slog.i(TAG, "Given Intent does not require a specific package: " - + intent); - // XXX we should really throw a security exception, if the caller's - // targetSdkVersion is high enough. - //throw new SecurityException("Given Intent does not require a specific package: " - // + intent); } - @Override - public void requestLocationUpdatesPI(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, PendingIntent intent) { - validatePendingIntent(intent); - if (criteria != null) { - // FIXME - should we consider using multiple providers simultaneously - // rather than only the best one? - // Should we do anything different for single shot fixes? - provider = getBestProvider(criteria, true); - if (provider == null) { - throw new IllegalArgumentException("no providers found for criteria"); - } - } - try { - synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, - getReceiver(intent)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "requestUpdates got exception:", e); + private Receiver checkListenerOrIntent(ILocationListener listener, PendingIntent intent, + int pid, int uid, String packageName) { + if (intent == null && listener == null) { + throw new IllegalArgumentException("need eiter listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException("cannot register both listener and intent"); + } else if (intent != null) { + checkPendingIntent(intent); + return getReceiver(intent, pid, uid, packageName); + } else { + return getReceiver(listener, pid, uid, packageName); } } - private void requestLocationUpdatesLocked(String provider, long minTime, float minDistance, - boolean singleShot, Receiver receiver) { + @Override + public void requestLocationUpdates(LocationRequest request, ILocationListener listener, + PendingIntent intent, String packageName) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPackageName(packageName); + checkPermissionAndRequest(request); - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - throw new IllegalArgumentException("requested provider " + provider + - " doesn't exisit"); - } - receiver.mRequiredPermissions = checkPermissionsSafe(provider, - receiver.mRequiredPermissions); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + Receiver recevier = checkListenerOrIntent(listener, intent, pid, uid, packageName); - // so wakelock calls will succeed - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - boolean newUid = !providerHasListener(provider, callingUid, null); + // so wakelock calls will succeed (not totally sure this is still needed) long identity = Binder.clearCallingIdentity(); try { - UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, singleShot, - receiver, callingUid); - UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r); - if (oldRecord != null) { - oldRecord.disposeLocked(); - } - - if (newUid) { - p.addListener(callingUid); - } - - boolean isProviderEnabled = isAllowedBySettingsLocked(provider); - if (isProviderEnabled) { - long minTimeForProvider = getMinTimeLocked(provider); - Slog.i(TAG, "request " + provider + " (pid " + callingPid + ") " + minTime + - " " + minTimeForProvider + (singleShot ? " (singleshot)" : "")); - p.setMinTime(minTimeForProvider, mTmpWorkSource); - // try requesting single shot if singleShot is true, and fall back to - // regular location tracking if requestSingleShotFix() is not supported - if (!singleShot || !p.requestSingleShotFix()) { - p.enableLocationTracking(true); - } - } else { - // Notify the listener that updates are currently disabled - receiver.callProviderEnabledLocked(provider, false); - } - if (LOCAL_LOGV) { - Slog.v(TAG, "_requestLocationUpdates: provider = " + provider + " listener = " + receiver); + synchronized (mLock) { + requestLocationUpdatesLocked(request, recevier, pid, uid, packageName); } } finally { Binder.restoreCallingIdentity(identity); } } - @Override - public void removeUpdates(ILocationListener listener) { - try { - synchronized (mLock) { - removeUpdatesLocked(getReceiver(listener)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeUpdates got exception:", e); + private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver, + int pid, int uid, String packageName) { + // Figure out the provider. Either its explicitly request (legacy use cases), or + // use the fused provider + if (request == null) request = DEFAULT_LOCATION_REQUEST; + String name = request.getProvider(); + if (name == null) name = LocationManager.FUSED_PROVIDER; + LocationProviderInterface provider = mProvidersByName.get(name); + if (provider == null) { + throw new IllegalArgumentException("provider doesn't exisit: " + provider); + } + + Log.i(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver)) + " " + + name + " " + request + " from " + packageName + "(" + uid + ")"); + + UpdateRecord record = new UpdateRecord(name, request, receiver); + UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record); + if (oldRecord != null) { + oldRecord.disposeLocked(false); + } + + boolean isProviderEnabled = isAllowedBySettingsLocked(name); + if (isProviderEnabled) { + applyRequirementsLocked(name); + } else { + // Notify the listener that updates are currently disabled + receiver.callProviderEnabledLocked(name, false); } } @Override - public void removeUpdatesPI(PendingIntent intent) { + public void removeUpdates(ILocationListener listener, PendingIntent intent, + String packageName) { + checkPackageName(packageName); + checkPermission(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + Receiver receiver = checkListenerOrIntent(listener, intent, pid, uid, packageName); + + // so wakelock calls will succeed (not totally sure this is still needed) + long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - removeUpdatesLocked(getReceiver(intent)); + removeUpdatesLocked(receiver); } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeUpdates got exception:", e); + } finally { + Binder.restoreCallingIdentity(identity); } } private void removeUpdatesLocked(Receiver receiver) { - if (LOCAL_LOGV) { - Slog.v(TAG, "_removeUpdates: listener = " + receiver); - } + Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver))); - // so wakelock calls will succeed - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - try { - if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { - receiver.getListener().asBinder().unlinkToDeath(receiver, 0); - synchronized(receiver) { - if(receiver.mPendingBroadcasts > 0) { - decrementPendingBroadcasts(); - receiver.mPendingBroadcasts = 0; - } + if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { + receiver.getListener().asBinder().unlinkToDeath(receiver, 0); + synchronized (receiver) { + if (receiver.mPendingBroadcasts > 0) { + decrementPendingBroadcasts(); + receiver.mPendingBroadcasts = 0; } } + } - // Record which providers were associated with this listener - HashSet<String> providers = new HashSet<String>(); - HashMap<String,UpdateRecord> oldRecords = receiver.mUpdateRecords; - if (oldRecords != null) { - // Call dispose() on the obsolete update records. - for (UpdateRecord record : oldRecords.values()) { - if (!providerHasListener(record.mProvider, callingUid, receiver)) { - LocationProviderInterface p = mProvidersByName.get(record.mProvider); - if (p != null) { - p.removeListener(callingUid); - } - } - record.disposeLocked(); - } - // Accumulate providers - providers.addAll(oldRecords.keySet()); + // Record which providers were associated with this listener + HashSet<String> providers = new HashSet<String>(); + HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords; + if (oldRecords != null) { + // Call dispose() on the obsolete update records. + for (UpdateRecord record : oldRecords.values()) { + record.disposeLocked(false); } + // Accumulate providers + providers.addAll(oldRecords.keySet()); + } - // See if the providers associated with this listener have any - // other listeners; if one does, inform it of the new smallest minTime - // value; if one does not, disable location tracking for it - for (String provider : providers) { - // If provider is already disabled, don't need to do anything - if (!isAllowedBySettingsLocked(provider)) { - continue; - } + // update provider + for (String provider : providers) { + // If provider is already disabled, don't need to do anything + if (!isAllowedBySettingsLocked(provider)) { + continue; + } - boolean hasOtherListener = false; - ArrayList<UpdateRecord> recordsForProvider = mRecordsByProvider.get(provider); - if (recordsForProvider != null && recordsForProvider.size() > 0) { - hasOtherListener = true; - } + applyRequirementsLocked(provider); + } + } - LocationProviderInterface p = mProvidersByName.get(provider); - if (p != null) { - if (hasOtherListener) { - long minTime = getMinTimeLocked(provider); - Slog.i(TAG, "remove " + provider + " (pid " + callingPid + - "), next minTime = " + minTime); - p.setMinTime(minTime, mTmpWorkSource); - } else { - Slog.i(TAG, "remove " + provider + " (pid " + callingPid + - "), disabled"); - p.enableLocationTracking(false); - } - } + @Override + public Location getLastLocation(LocationRequest request) { + if (D) Log.d(TAG, "getLastLocation: " + request); + if (request == null) request = DEFAULT_LOCATION_REQUEST; + String perm = checkPermissionAndRequest(request); + + synchronized (mLock) { + // Figure out the provider. Either its explicitly request (deprecated API's), + // or use the fused provider + String name = request.getProvider(); + if (name == null) name = LocationManager.FUSED_PROVIDER; + LocationProviderInterface provider = mProvidersByName.get(name); + if (provider == null) return null; + + if (!isAllowedBySettingsLocked(name)) return null; + + Location location = mLastLocation.get(name); + if (ACCESS_FINE_LOCATION.equals(perm)) { + return location; + } else { + return getCoarseLocationExtra(location); } - } finally { - Binder.restoreCallingIdentity(identity); } } @Override + public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent, + String packageName) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPermissionAndRequest(request); + checkPendingIntent(intent); + checkPackageName(packageName); + + if (D) Log.d(TAG, "requestGeofence: " + request + " " + geofence + " " + intent); + + mGeofenceManager.addFence(request, geofence, intent, Binder.getCallingUid(), packageName); + } + + @Override + public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) { + checkPermission(); + checkPendingIntent(intent); + checkPackageName(packageName); + + if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); + + mGeofenceManager.removeFence(geofence, intent); + } + + + @Override public boolean addGpsStatusListener(IGpsStatusListener listener) { if (mGpsStatusProvider == null) { return false; @@ -1405,8 +1146,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new NullPointerException(); } - // first check for permission to the provider - checkPermissionsSafe(provider, null); + checkPermission(); // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PackageManager.PERMISSION_GRANTED)) { @@ -1415,176 +1155,113 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (mLock) { LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return false; - } + if (p == null) return false; return p.sendExtraCommand(command, extras); } } @Override - public boolean sendNiResponse(int notifId, int userResponse) - { + public boolean sendNiResponse(int notifId, int userResponse) { if (Binder.getCallingUid() != Process.myUid()) { throw new SecurityException( "calling sendNiResponse from outside of the system is not allowed"); } try { return mNetInitiatedListener.sendNiResponse(notifId, userResponse); - } - catch (RemoteException e) - { + } catch (RemoteException e) { Slog.e(TAG, "RemoteException in LocationManagerService.sendNiResponse"); return false; } } - @Override - public void addProximityAlert(double latitude, double longitude, - float radius, long expiration, PendingIntent intent, String packageName) { - validatePendingIntent(intent); - validatePackageName(Binder.getCallingUid(), packageName); - - // Require ability to access all providers for now - if (!isAllowedProviderSafe(LocationManager.GPS_PROVIDER) || - !isAllowedProviderSafe(LocationManager.NETWORK_PROVIDER)) { - throw new SecurityException("Requires ACCESS_FINE_LOCATION permission"); - } - - if (LOCAL_LOGV) Slog.v(TAG, "addProximityAlert: lat=" + latitude + ", long=" + longitude + - ", radius=" + radius + ", exp=" + expiration + ", intent = " + intent); - - mGeofenceManager.addFence(latitude, longitude, radius, expiration, intent, - Binder.getCallingUid(), packageName); - } - - @Override - public void removeProximityAlert(PendingIntent intent) { - if (intent == null) throw new NullPointerException("pending intent is null"); - - if (LOCAL_LOGV) Slog.v(TAG, "removeProximityAlert: intent = " + intent); - - mGeofenceManager.removeFence(intent); - } - /** * @return null if the provider does not exist * @throws SecurityException if the provider is not allowed to be * accessed by the caller */ @Override - public Bundle getProviderInfo(String provider) { - try { - synchronized (mLock) { - return _getProviderInfoLocked(provider); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "_getProviderInfo got exception:", e); - return null; - } - } + public ProviderProperties getProviderProperties(String provider) { + checkPermission(); - private Bundle _getProviderInfoLocked(String provider) { - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return null; + LocationProviderInterface p; + synchronized (mLock) { + p = mProvidersByName.get(provider); } - checkPermissionsSafe(provider, null); - - Bundle b = new Bundle(); - b.putBoolean("network", p.requiresNetwork()); - b.putBoolean("satellite", p.requiresSatellite()); - b.putBoolean("cell", p.requiresCell()); - b.putBoolean("cost", p.hasMonetaryCost()); - b.putBoolean("altitude", p.supportsAltitude()); - b.putBoolean("speed", p.supportsSpeed()); - b.putBoolean("bearing", p.supportsBearing()); - b.putInt("power", p.getPowerRequirement()); - b.putInt("accuracy", p.getAccuracy()); - - return b; + if (p == null) return null; + return p.getProperties(); } @Override public boolean isProviderEnabled(String provider) { - try { - synchronized (mLock) { - return _isProviderEnabledLocked(provider); - } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "isProviderEnabled got exception:", e); - return false; + checkPermission(); + if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; + + synchronized (mLock) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) return false; + + return isAllowedBySettingsLocked(provider); } } - @Override - public void reportLocation(Location location, boolean passive) { + private void checkCallerIsProvider() { if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires INSTALL_LOCATION_PROVIDER permission"); - } - - if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); + == PackageManager.PERMISSION_GRANTED) { return; } - mLocationHandler.removeMessages(MESSAGE_LOCATION_CHANGED, location); - Message m = Message.obtain(mLocationHandler, MESSAGE_LOCATION_CHANGED, location); - m.arg1 = (passive ? 1 : 0); - mLocationHandler.sendMessageAtFrontOfQueue(m); - } + // Previously we only used the INSTALL_LOCATION_PROVIDER + // check. But that is system or signature + // protection level which is not flexible enough for + // providers installed oustide the system image. So + // also allow providers with a UID matching the + // currently bound package name - private boolean _isProviderEnabledLocked(String provider) { - checkPermissionsSafe(provider, null); + int uid = Binder.getCallingUid(); - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return false; + if (mGeocodeProvider != null) { + if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return; + } + for (LocationProviderProxy proxy : mProxyProviders) { + if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return; } - return isAllowedBySettingsLocked(provider); + throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " + + "or UID of a currently bound location provider"); } - @Override - public Location getLastKnownLocation(String provider) { - if (LOCAL_LOGV) { - Slog.v(TAG, "getLastKnownLocation: " + provider); + private boolean doesPackageHaveUid(int uid, String packageName) { + if (packageName == null) { + return false; } try { - synchronized (mLock) { - return _getLastKnownLocationLocked(provider); + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0); + if (appInfo.uid != uid) { + return false; } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getLastKnownLocation got exception:", e); - return null; + } catch (NameNotFoundException e) { + return false; } + return true; } - private Location _getLastKnownLocationLocked(String provider) { - checkPermissionsSafe(provider, null); - - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return null; - } + @Override + public void reportLocation(Location location, boolean passive) { + checkCallerIsProvider(); - if (!isAllowedBySettingsLocked(provider)) { - return null; + if (!location.isComplete()) { + Log.w(TAG, "Dropping incomplete location: " + location); + return; } - return mLastKnownLocation.get(provider); + mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location); + Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location); + m.arg1 = (passive ? 1 : 0); + mLocationHandler.sendMessageAtFrontOfQueue(m); } + private static boolean shouldBroadcastSafe(Location loc, Location lastLoc, UpdateRecord record) { // Always broadcast the first update if (lastLoc == null) { @@ -1592,14 +1269,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } // Check whether sufficient time has passed - long minTime = record.mMinTime; + long minTime = record.mRequest.getFastestInterval(); long delta = (loc.getElapsedRealtimeNano() - lastLoc.getElapsedRealtimeNano()) / 1000000L; - if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER) { + if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) { return false; } // Check whether sufficient distance has been traveled - double minDistance = record.mMinDistance; + double minDistance = record.mRequest.getSmallestDisplacement(); if (minDistance > 0.0) { if (loc.distanceTo(lastLoc) <= minDistance) { return false; @@ -1610,24 +1287,27 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } private void handleLocationChangedLocked(Location location, boolean passive) { + long now = SystemClock.elapsedRealtime(); String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - if (records == null || records.size() == 0) { - return; - } + if (records == null || records.size() == 0) return; LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return; + if (p == null) return; + + // Add the coarse location as an extra, if not already present + Location coarse = getCoarseLocationExtra(location); + if (coarse == null) { + coarse = addCoarseLocationExtra(location); } - // Update last known location for provider - Location lastLocation = mLastKnownLocation.get(provider); + // Update last known locations + Location lastLocation = mLastLocation.get(provider); if (lastLocation == null) { - mLastKnownLocation.put(provider, new Location(location)); - } else { - lastLocation.set(location); + lastLocation = new Location(provider); + mLastLocation.put(provider, lastLocation); } + lastLocation.set(location); // Fetch latest status update time long newStatusUpdateTime = p.getStatusUpdateTime(); @@ -1637,13 +1317,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run int status = p.getStatus(extras); ArrayList<Receiver> deadReceivers = null; + ArrayList<UpdateRecord> deadUpdateRecords = null; // Broadcast location or status to all listeners - final int N = records.size(); - for (int i=0; i<N; i++) { - UpdateRecord r = records.get(i); + for (UpdateRecord r : records) { Receiver receiver = r.mReceiver; boolean receiverDead = false; + if (ACCESS_FINE_LOCATION.equals(receiver.mPermission)) { + location = lastLocation; // use fine location + } else { + location = coarse; // use coarse location + } Location lastLoc = r.mLastFixBroadcast; if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) { @@ -1670,8 +1354,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - // remove receiver if it is dead or we just processed a single shot request - if (receiverDead || r.mSingleShot) { + // track expired records + if (r.mRequest.getNumUpdates() == 0 || r.mRequest.getExpireAt() < now) { + if (deadUpdateRecords == null) { + deadUpdateRecords = new ArrayList<UpdateRecord>(); + } + deadUpdateRecords.add(r); + } + // track dead receivers + if (receiverDead) { if (deadReceivers == null) { deadReceivers = new ArrayList<Receiver>(); } @@ -1681,162 +1372,71 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + // remove dead records and receivers outside the loop if (deadReceivers != null) { - for (int i=deadReceivers.size()-1; i>=0; i--) { - removeUpdatesLocked(deadReceivers.get(i)); + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); + } + } + if (deadUpdateRecords != null) { + for (UpdateRecord r : deadUpdateRecords) { + r.disposeLocked(true); } } } private class LocationWorkerHandler extends Handler { - @Override public void handleMessage(Message msg) { - try { - if (msg.what == MESSAGE_LOCATION_CHANGED) { - // log("LocationWorkerHandler: MESSAGE_LOCATION_CHANGED!"); - - synchronized (mLock) { - Location location = (Location) msg.obj; - String provider = location.getProvider(); - boolean passive = (msg.arg1 == 1); - - if (!passive) { - // notify other providers of the new location - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - if (!provider.equals(p.getName())) { - p.updateLocation(location); - } - } - } + switch (msg.what) { + case MSG_LOCATION_CHANGED: + handleLocationChanged((Location) msg.obj, msg.arg1 == 1); + break; + } + } + } - if (isAllowedBySettingsLocked(provider)) { - handleLocationChangedLocked(location, passive); - } - } - } else if (msg.what == MESSAGE_PACKAGE_UPDATED) { - String packageName = (String) msg.obj; - - // reconnect to external providers if there is a better package - if (mNetworkLocationProviderPackageName != null && - mPackageManager.resolveService( - new Intent(LocationProviderProxy.SERVICE_ACTION) - .setPackage(packageName), 0) != null) { - // package implements service, perform full check - String bestPackage = findBestPackage( - LocationProviderProxy.SERVICE_ACTION, - mNetworkLocationProviderPackageName); - if (packageName.equals(bestPackage)) { - mNetworkLocationProvider.reconnect(bestPackage); - mNetworkLocationProviderPackageName = packageName; - } - } - if (mGeocodeProviderPackageName != null && - mPackageManager.resolveService( - new Intent(GeocoderProxy.SERVICE_ACTION) - .setPackage(packageName), 0) != null) { - // package implements service, perform full check - String bestPackage = findBestPackage( - GeocoderProxy.SERVICE_ACTION, - mGeocodeProviderPackageName); - if (packageName.equals(bestPackage)) { - mGeocodeProvider.reconnect(bestPackage); - mGeocodeProviderPackageName = packageName; - } - } - } - } catch (Exception e) { - // Log, don't crash! - Slog.e(TAG, "Exception in LocationWorkerHandler.handleMessage:", e); + private void handleLocationChanged(Location location, boolean passive) { + String provider = location.getProvider(); + + if (!passive) { + // notify passive provider of the new location + mPassiveProvider.updateLocation(location); + } + + synchronized (mLock) { + if (isAllowedBySettingsLocked(provider)) { + handleLocationChangedLocked(location, passive); } } } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - boolean queryRestart = action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART); - if (queryRestart - || action.equals(Intent.ACTION_PACKAGE_REMOVED) - || action.equals(Intent.ACTION_PACKAGE_RESTARTED) - || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - synchronized (mLock) { - int uidList[] = null; - if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); - } else { - uidList = new int[]{intent.getIntExtra(Intent.EXTRA_UID, -1)}; - } - if (uidList == null || uidList.length == 0) { - return; - } - for (int uid : uidList) { - if (uid >= 0) { - ArrayList<Receiver> removedRecs = null; - for (ArrayList<UpdateRecord> i : mRecordsByProvider.values()) { - for (int j=i.size()-1; j>=0; j--) { - UpdateRecord ur = i.get(j); - if (ur.mReceiver.isPendingIntent() && ur.mUid == uid) { - if (queryRestart) { - setResultCode(Activity.RESULT_OK); - return; - } - if (removedRecs == null) { - removedRecs = new ArrayList<Receiver>(); - } - if (!removedRecs.contains(ur.mReceiver)) { - removedRecs.add(ur.mReceiver); - } - } - } - } + public void onPackageDisappeared(String packageName, int reason) { + // remove all receivers associated with this package name + synchronized (mLock) { + ArrayList<Receiver> deadReceivers = null; + + for (Receiver receiver : mReceivers.values()) { + if (receiver.mPackageName.equals(packageName)) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<Receiver>(); } + deadReceivers.add(receiver); } } - } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - boolean noConnectivity = - intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - if (!noConnectivity) { - mNetworkState = LocationProvider.AVAILABLE; - } else { - mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; - } - - final NetworkInfo info = intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); - // Notify location providers of current network state - synchronized (mLock) { - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface provider = mProviders.get(i); - if (provider.requiresNetwork()) { - provider.updateNetworkState(mNetworkState, info); - } + // perform removal outside of mReceivers loop + if (deadReceivers != null) { + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); } } } } }; - private final PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - // Called by main thread; divert work to LocationWorker. - Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); - } - @Override - public void onPackageAdded(String packageName, int uid) { - // Called by main thread; divert work to LocationWorker. - Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); - } - @Override - public void onPackageDisappeared(String packageName, int uid) { - mGeofenceManager.removeFence(packageName); - } - }; - // Wake locks private void incrementPendingBroadcasts() { @@ -1922,9 +1522,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } @Override - public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + public void addTestProvider(String name, ProviderProperties properties) { checkMockPermissionsSafe(); if (LocationManager.PASSIVE_PROVIDER.equals(name)) { @@ -1933,25 +1531,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run long identity = Binder.clearCallingIdentity(); synchronized (mLock) { - MockProvider provider = new MockProvider(name, this, - requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, - supportsSpeed, supportsBearing, powerRequirement, accuracy); + MockProvider provider = new MockProvider(name, this, properties); // remove the real provider if we are replacing GPS or network provider if (LocationManager.GPS_PROVIDER.equals(name) || LocationManager.NETWORK_PROVIDER.equals(name)) { LocationProviderInterface p = mProvidersByName.get(name); if (p != null) { - p.enableLocationTracking(false); - removeProvider(p); + removeProviderLocked(p); } } if (mProvidersByName.get(name) != null) { throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); } - addProvider(provider); + addProviderLocked(provider); mMockProviders.put(name, provider); - mLastKnownLocation.put(name, null); + mLastLocation.put(name, null); updateProvidersLocked(); } Binder.restoreCallingIdentity(identity); @@ -1966,17 +1560,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } long identity = Binder.clearCallingIdentity(); - removeProvider(mProvidersByName.get(provider)); + removeProviderLocked(mProvidersByName.get(provider)); mMockProviders.remove(mockProvider); - // reinstall real provider if we were mocking GPS or network provider - if (LocationManager.GPS_PROVIDER.equals(provider) && - mGpsLocationProvider != null) { - addProvider(mGpsLocationProvider); - } else if (LocationManager.NETWORK_PROVIDER.equals(provider) && - mNetworkLocationProvider != null) { - addProvider(mNetworkLocationProvider); - } - mLastKnownLocation.put(provider, null); + + // reinstate real provider if available + LocationProviderInterface realProvider = mRealProviders.get(provider); + if (realProvider != null) { + addProviderLocked(realProvider); + } + mLastLocation.put(provider, null); updateProvidersLocked(); Binder.restoreCallingIdentity(identity); } @@ -2072,6 +1664,106 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + private static double wrapLatitude(double lat) { + if (lat > MAX_LATITUDE) lat = MAX_LATITUDE; + if (lat < -MAX_LATITUDE) lat = -MAX_LATITUDE; + return lat; + } + + private static double wrapLongitude(double lon) { + if (lon >= 180.0) lon -= 360.0; + if (lon < -180.0) lon += 360.0; + return lon; + } + + private static double distanceToDegreesLatitude(double distance) { + return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR; + } + + /** + * Requires latitude since longitudinal distances change with distance from equator. + */ + private static double distanceToDegreesLongitude(double distance, double lat) { + return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(lat); + } + + /** + * Fudge a location into a coarse location. + * <p>Add a random offset, then quantize the result (snap-to-grid). + * Random offsets alone can be low-passed pretty easily. + * Snap-to-grid on its own is excellent unless you are sitting on a + * grid boundary and bouncing between quantizations. + * The combination is quite hard to reverse engineer. + * <p>The random offset used is smaller than the goal accuracy + * ({@link #COARSE_ACCURACY_M}), in order to give relatively stable + * results after quantization. + */ + private static Location createCoarse(Location fine) { + Location coarse = new Location(fine); + + coarse.removeBearing(); + coarse.removeSpeed(); + coarse.removeAltitude(); + + double lat = coarse.getLatitude(); + double lon = coarse.getLongitude(); + + // wrap + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + if (coarse.getAccuracy() < COARSE_ACCURACY_M / 2) { + // apply a random offset + double fudgeDistance = COARSE_ACCURACY_M / 2.0 - coarse.getAccuracy(); + lat += (Math.random() - 0.5) * distanceToDegreesLatitude(fudgeDistance); + lon += (Math.random() - 0.5) * distanceToDegreesLongitude(fudgeDistance, lat); + } + + // wrap + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // quantize (snap-to-grid) + double latGranularity = distanceToDegreesLatitude(COARSE_ACCURACY_M); + double lonGranularity = distanceToDegreesLongitude(COARSE_ACCURACY_M, lat); + long latQuantized = Math.round(lat / latGranularity); + long lonQuantized = Math.round(lon / lonGranularity); + lat = latQuantized * latGranularity; + lon = lonQuantized * lonGranularity; + + // wrap again + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // apply + coarse.setLatitude(lat); + coarse.setLongitude(lon); + coarse.setAccuracy((float)COARSE_ACCURACY_M); + + return coarse; + } + + + private static Location getCoarseLocationExtra(Location location) { + Bundle extras = location.getExtras(); + if (extras == null) return null; + Parcelable parcel = extras.getParcelable(EXTRA_COARSE_LOCATION); + if (parcel == null) return null; + if (!(parcel instanceof Location)) return null; + Location coarse = (Location) parcel; + if (coarse.getAccuracy() < COARSE_ACCURACY_M) return null; + return coarse; + } + + private static Location addCoarseLocationExtra(Location location) { + Bundle extras = location.getExtras(); + if (extras == null) extras = new Bundle(); + Location coarse = createCoarse(location); + extras.putParcelable(EXTRA_COARSE_LOCATION, coarse); + location.setExtras(extras); + return coarse; + } + private void log(String log) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Slog.d(TAG, log); @@ -2090,36 +1782,26 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (mLock) { pw.println("Current Location Manager state:"); - pw.println(" sProvidersLoaded=" + sProvidersLoaded); - pw.println(" Listeners:"); - int N = mReceivers.size(); - for (int i=0; i<N; i++) { - pw.println(" " + mReceivers.get(i)); - } pw.println(" Location Listeners:"); - for (Receiver i : mReceivers.values()) { - pw.println(" " + i + ":"); - for (Map.Entry<String,UpdateRecord> j : i.mUpdateRecords.entrySet()) { - pw.println(" " + j.getKey() + ":"); - j.getValue().dump(pw, " "); - } + for (Receiver receiver : mReceivers.values()) { + pw.println(" " + receiver); } pw.println(" Records by Provider:"); - for (Map.Entry<String, ArrayList<UpdateRecord>> i - : mRecordsByProvider.entrySet()) { - pw.println(" " + i.getKey() + ":"); - for (UpdateRecord j : i.getValue()) { - pw.println(" " + j + ":"); - j.dump(pw, " "); + for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + pw.println(" " + entry.getKey() + ":"); + for (UpdateRecord record : entry.getValue()) { + pw.println(" " + record); } } pw.println(" Last Known Locations:"); - for (Map.Entry<String, Location> i - : mLastKnownLocation.entrySet()) { - pw.println(" " + i.getKey() + ":"); - i.getValue().dump(new PrintWriterPrinter(pw), " "); + for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) { + String provider = entry.getKey(); + Location location = entry.getValue(); + pw.println(" " + provider + ": " + location); } + mGeofenceManager.dump(pw); + if (mEnabledProviders.size() > 0) { pw.println(" Enabled Providers:"); for (String i : mEnabledProviders) { @@ -2140,12 +1822,18 @@ public class LocationManagerService extends ILocationManager.Stub implements Run i.getValue().dump(pw, " "); } } + + if (args.length > 0 && "short".equals(args[0])) { + return; + } for (LocationProviderInterface provider: mProviders) { - String state = provider.getInternalState(); - if (state != null) { - pw.println(provider.getName() + " Internal State:"); - pw.write(state); + pw.print(provider.getName() + " Internal State"); + if (provider instanceof LocationProviderProxy) { + LocationProviderProxy proxy = (LocationProviderProxy) provider; + pw.print(" (" + proxy.getConnectedPackageName() + ")"); } + pw.println(":"); + provider.dump(fd, pw, args); } } } |