diff options
Diffstat (limited to 'services')
-rw-r--r-- | services/java/com/android/server/LocationManagerService.java | 1926 | ||||
-rw-r--r-- | services/java/com/android/server/ServiceWatcher.java | 274 | ||||
-rw-r--r-- | services/java/com/android/server/location/GeocoderProxy.java | 92 | ||||
-rw-r--r-- | services/java/com/android/server/location/GeofenceManager.java | 188 | ||||
-rw-r--r-- | services/java/com/android/server/location/GeofenceState.java (renamed from services/java/com/android/server/location/Geofence.java) | 40 | ||||
-rwxr-xr-x | services/java/com/android/server/location/GpsLocationProvider.java | 549 | ||||
-rw-r--r-- | services/java/com/android/server/location/LocationProviderInterface.java | 53 | ||||
-rw-r--r-- | services/java/com/android/server/location/LocationProviderProxy.java | 570 | ||||
-rw-r--r-- | services/java/com/android/server/location/MockProvider.java | 156 | ||||
-rw-r--r-- | services/java/com/android/server/location/PassiveProvider.java | 94 |
10 files changed, 1743 insertions, 2199 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); } } } diff --git a/services/java/com/android/server/ServiceWatcher.java b/services/java/com/android/server/ServiceWatcher.java new file mode 100644 index 0000000..0dfaa05 --- /dev/null +++ b/services/java/com/android/server/ServiceWatcher.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.content.PackageMonitor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Find the best Service, and bind to it. + * Handles run-time package changes. + */ +public class ServiceWatcher implements ServiceConnection { + private static final boolean D = false; + private static final String EXTRA_VERSION = "version"; + + private final String mTag; + private final Context mContext; + private final PackageManager mPm; + private final List<HashSet<Signature>> mSignatureSets; + private final String mAction; + private final Runnable mNewServiceWork; + private final Handler mHandler; + + private Object mLock = new Object(); + + // all fields below synchronized on mLock + private IBinder mBinder; // connected service + private String mPackageName; // current best package + private int mVersion; // current best version + + public ServiceWatcher(Context context, String logTag, String action, + List<String> initialPackageNames, Runnable newServiceWork, Handler handler) { + mContext = context; + mTag = logTag; + mAction = action; + mPm = mContext.getPackageManager(); + mNewServiceWork = newServiceWork; + mHandler = handler; + + mSignatureSets = new ArrayList<HashSet<Signature>>(); + for (int i=0; i < initialPackageNames.size(); i++) { + String pkg = initialPackageNames.get(i); + HashSet<Signature> set = new HashSet<Signature>(); + try { + Signature[] sigs = + mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures; + set.addAll(Arrays.asList(sigs)); + mSignatureSets.add(set); + } catch (NameNotFoundException e) { + Log.w(logTag, pkg + " not found"); + } + } + + } + + public boolean start() { + if (!bindBestPackage(null)) return false; + + mPackageMonitor.register(mContext, null, true); + return true; + } + + /** + * Searches and binds to the best package, or do nothing + * if the best package is already bound. + * Only checks the named package, or checks all packages if it + * is null. + * Return true if a new package was found to bind to. + */ + private boolean bindBestPackage(String justCheckThisPackage) { + Intent intent = new Intent(mAction); + if (justCheckThisPackage != null) { + intent.setPackage(justCheckThisPackage); + } + List<ResolveInfo> rInfos = mPm.queryIntentServices(new Intent(mAction), + PackageManager.GET_META_DATA); + int bestVersion = Integer.MIN_VALUE; + String bestPackage = null; + for (ResolveInfo rInfo : rInfos) { + String packageName = rInfo.serviceInfo.packageName; + + // check signature + try { + PackageInfo pInfo; + pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + if (!isSignatureMatch(pInfo.signatures)) { + Log.w(mTag, packageName + " resolves service " + mAction + + ", but has wrong signature, ignoring"); + continue; + } + } catch (NameNotFoundException e) { + Log.wtf(mTag, e); + continue; + } + + // check version + int version = 0; + if (rInfo.serviceInfo.metaData != null) { + version = rInfo.serviceInfo.metaData.getInt(EXTRA_VERSION, 0); + } + if (version > mVersion) { + bestVersion = version; + bestPackage = packageName; + } + } + + if (D) Log.d(mTag, String.format("bindBestPackage %s found %d, %s", + (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "), + rInfos.size(), + (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage))); + + if (bestPackage != null) { + bindToPackage(bestPackage, bestVersion); + return true; + } + return false; + } + + private void unbind() { + String pkg; + synchronized (mLock) { + pkg = mPackageName; + mPackageName = null; + mVersion = Integer.MIN_VALUE; + } + if (pkg != null) { + if (D) Log.d(mTag, "unbinding " + pkg); + mContext.unbindService(this); + } + } + + private void bindToPackage(String packageName, int version) { + unbind(); + Intent intent = new Intent(mAction); + intent.setPackage(packageName); + synchronized (mLock) { + mPackageName = packageName; + mVersion = version; + } + if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")"); + mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); + } + + private boolean isSignatureMatch(Signature[] signatures) { + if (signatures == null) return false; + + // build hashset of input to test against + HashSet<Signature> inputSet = new HashSet<Signature>(); + for (Signature s : signatures) { + inputSet.add(s); + } + + // test input against each of the signature sets + for (HashSet<Signature> referenceSet : mSignatureSets) { + if (referenceSet.equals(inputSet)) { + return true; + } + } + return false; + } + + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + /** + * Called when package has been reinstalled + */ + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + // package updated, make sure to rebind + unbind(); + } + // check the updated package in case it is better + bindBestPackage(packageName); + } + + @Override + public void onPackageAdded(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + // package updated, make sure to rebind + unbind(); + } + // check the new package is case it is better + bindBestPackage(packageName); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + unbind(); + // the currently bound package was removed, + // need to search for a new package + bindBestPackage(null); + } + } + }; + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + synchronized (mLock) { + String packageName = name.getPackageName(); + if (packageName.equals(mPackageName)) { + if (D) Log.d(mTag, packageName + " connected"); + mBinder = binder; + if (mHandler !=null && mNewServiceWork != null) { + mHandler.post(mNewServiceWork); + } + } else { + Log.w(mTag, "unexpected onServiceConnected: " + packageName); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + String packageName = name.getPackageName(); + if (D) Log.d(mTag, packageName + " disconnected"); + + if (packageName.equals(mPackageName)) { + mBinder = null; + } + } + } + + public String getBestPackageName() { + synchronized (mLock) { + return mPackageName; + } + } + + public int getBestVersion() { + synchronized (mLock) { + return mVersion; + } + } + + public IBinder getBinder() { + synchronized (mLock) { + return mBinder; + } + } +} diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java index 07f3125..7d030e9 100644 --- a/services/java/com/android/server/location/GeocoderProxy.java +++ b/services/java/com/android/server/location/GeocoderProxy.java @@ -16,92 +16,64 @@ package com.android.server.location; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.location.Address; import android.location.GeocoderParams; import android.location.IGeocodeProvider; -import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; +import com.android.server.ServiceWatcher; import java.util.List; /** - * A class for proxying IGeocodeProvider implementations. - * - * {@hide} + * Proxy for IGeocodeProvider implementations. */ public class GeocoderProxy { - private static final String TAG = "GeocoderProxy"; - public static final String SERVICE_ACTION = - "com.android.location.service.GeocodeProvider"; + private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider"; private final Context mContext; - private final Intent mIntent; - private final Object mMutex = new Object(); // synchronizes access to mServiceConnection - private Connection mServiceConnection; // never null after ctor - - public GeocoderProxy(Context context, String packageName) { - mContext = context; - mIntent = new Intent(SERVICE_ACTION); - reconnect(packageName); - } - - /** Bind to service. Will reconnect if already connected */ - public void reconnect(String packageName) { - synchronized (mMutex) { - if (mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - } - mServiceConnection = new Connection(); - mIntent.setPackage(packageName); - mContext.bindService(mIntent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_ALLOW_OOM_MANAGEMENT); + private final ServiceWatcher mServiceWatcher; + + public static GeocoderProxy createAndBind(Context context, + List<String> initialPackageNames) { + GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames); + if (proxy.bind()) { + return proxy; + } else { + return null; } } - private class Connection implements ServiceConnection { + public GeocoderProxy(Context context, List<String> initialPackageNames) { + mContext = context; - private IGeocodeProvider mProvider; + mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, initialPackageNames, + null, null); + } - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (this) { - mProvider = IGeocodeProvider.Stub.asInterface(service); - } - } + private boolean bind () { + return mServiceWatcher.start(); + } - public void onServiceDisconnected(ComponentName className) { - synchronized (this) { - mProvider = null; - } - } + private IGeocodeProvider getService() { + return IGeocodeProvider.Stub.asInterface(mServiceWatcher.getBinder()); + } - public IGeocodeProvider getProvider() { - synchronized (this) { - return mProvider; - } - } + public String getConnectedPackageName() { + return mServiceWatcher.getBestPackageName(); } public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { - IGeocodeProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } + IGeocodeProvider provider = getService(); if (provider != null) { try { - return provider.getFromLocation(latitude, longitude, maxResults, - params, addrs); + return provider.getFromLocation(latitude, longitude, maxResults, params, addrs); } catch (RemoteException e) { - Log.e(TAG, "getFromLocation failed", e); + Log.w(TAG, e); } } return "Service not Available"; @@ -111,19 +83,17 @@ public class GeocoderProxy { double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) { - IGeocodeProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } + IGeocodeProvider provider = getService(); if (provider != null) { try { return provider.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, maxResults, params, addrs); } catch (RemoteException e) { - Log.e(TAG, "getFromLocationName failed", e); + Log.w(TAG, e); } } return "Service not Available"; } + } diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java index b3f53ea..338cd5d 100644 --- a/services/java/com/android/server/location/GeofenceManager.java +++ b/services/java/com/android/server/location/GeofenceManager.java @@ -25,47 +25,36 @@ import android.Manifest.permission; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.location.Geofence; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; +import android.location.LocationRequest; import android.os.Bundle; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { - static final String TAG = "GeofenceManager"; + private static final String TAG = "GeofenceManager"; /** * Assume a maximum land speed, as a heuristic to throttle location updates. * (Air travel should result in an airplane mode toggle which will * force a new location update anyway). */ - static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) - - class GeofenceWrapper { - final Geofence fence; - final long expiry; - final String packageName; - final PendingIntent intent; - - public GeofenceWrapper(Geofence fence, long expiry, String packageName, - PendingIntent intent) { - this.fence = fence; - this.expiry = expiry; - this.packageName = packageName; - this.intent = intent; - } - } + private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) - final Context mContext; - final LocationManager mLocationManager; - final PowerManager.WakeLock mWakeLock; - final Looper mLooper; // looper thread to take location updates on + private final Context mContext; + private final LocationManager mLocationManager; + private final PowerManager.WakeLock mWakeLock; + private final Looper mLooper; // looper thread to take location updates on - // access to members below is synchronized on this - Location mLastLocation; - List<GeofenceWrapper> mFences = new LinkedList<GeofenceWrapper>(); + private Object mLock = new Object(); + + // access to members below is synchronized on mLock + private Location mLastLocation; + private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); public GeofenceManager(Context context) { mContext = context; @@ -73,82 +62,98 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mLooper = Looper.myLooper(); - mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this); - } - public void addFence(double latitude, double longitude, float radius, long expiration, - PendingIntent intent, int uid, String packageName) { - long expiry = SystemClock.elapsedRealtime() + expiration; - if (expiration < 0) { - expiry = Long.MAX_VALUE; - } - Geofence fence = new Geofence(latitude, longitude, radius, mLastLocation); - GeofenceWrapper fenceWrapper = new GeofenceWrapper(fence, expiry, packageName, intent); - - synchronized (this) { - mFences.add(fenceWrapper); - updateProviderRequirements(); + LocationRequest request = new LocationRequest() + .setQuality(LocationRequest.POWER_NONE) + .setFastestInterval(0); + mLocationManager.requestLocationUpdates(request, this, Looper.myLooper()); + } + + public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid, + String packageName) { + GeofenceState state = new GeofenceState(geofence, mLastLocation, + request.getExpireAt(), packageName, intent); + + synchronized (mLock) { + // first make sure it doesn't already exist + for (int i = mFences.size() - 1; i >= 0; i--) { + GeofenceState w = mFences.get(i); + if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) { + // already exists, remove the old one + mFences.remove(i); + break; + } + } + mFences.add(state); + updateProviderRequirementsLocked(); } } - public void removeFence(PendingIntent intent) { - synchronized (this) { - Iterator<GeofenceWrapper> iter = mFences.iterator(); + public void removeFence(Geofence fence, PendingIntent intent) { + synchronized (mLock) { + Iterator<GeofenceState> iter = mFences.iterator(); while (iter.hasNext()) { - GeofenceWrapper fenceWrapper = iter.next(); - if (fenceWrapper.intent.equals(intent)) { - iter.remove(); + GeofenceState state = iter.next(); + if (state.mIntent.equals(intent)) { + + if (fence == null) { + // alwaus remove + iter.remove(); + } else { + // just remove matching fences + if (fence.equals(state.mFence)) { + iter.remove(); + } + } } } - updateProviderRequirements(); + updateProviderRequirementsLocked(); } } public void removeFence(String packageName) { - synchronized (this) { - Iterator<GeofenceWrapper> iter = mFences.iterator(); + synchronized (mLock) { + Iterator<GeofenceState> iter = mFences.iterator(); while (iter.hasNext()) { - GeofenceWrapper fenceWrapper = iter.next(); - if (fenceWrapper.packageName.equals(packageName)) { + GeofenceState state = iter.next(); + if (state.mPackageName.equals(packageName)) { iter.remove(); } } - updateProviderRequirements(); + updateProviderRequirementsLocked(); } } - void removeExpiredFences() { - synchronized (this) { - long time = SystemClock.elapsedRealtime(); - Iterator<GeofenceWrapper> iter = mFences.iterator(); - while (iter.hasNext()) { - GeofenceWrapper fenceWrapper = iter.next(); - if (fenceWrapper.expiry < time) { - iter.remove(); - } + private void removeExpiredFencesLocked() { + long time = SystemClock.elapsedRealtime(); + Iterator<GeofenceState> iter = mFences.iterator(); + while (iter.hasNext()) { + GeofenceState state = iter.next(); + if (state.mExpireAt < time) { + iter.remove(); } } } - void processLocation(Location location) { + private void processLocation(Location location) { List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); - synchronized (this) { + synchronized (mLock) { mLastLocation = location; - removeExpiredFences(); + removeExpiredFencesLocked(); - for (GeofenceWrapper fenceWrapper : mFences) { - int event = fenceWrapper.fence.processLocation(location); - if ((event & Geofence.FLAG_ENTER) != 0) { - enterIntents.add(fenceWrapper.intent); + for (GeofenceState state : mFences) { + int event = state.processLocation(location); + if ((event & GeofenceState.FLAG_ENTER) != 0) { + enterIntents.add(state.mIntent); } - if ((event & Geofence.FLAG_EXIT) != 0) { - exitIntents.add(fenceWrapper.intent); + if ((event & GeofenceState.FLAG_EXIT) != 0) { + exitIntents.add(state.mIntent); } } - updateProviderRequirements(); + updateProviderRequirementsLocked(); } // release lock before sending intents @@ -160,52 +165,50 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish } } - void sendIntentEnter(PendingIntent pendingIntent) { + private void sendIntentEnter(PendingIntent pendingIntent) { Intent intent = new Intent(); intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); sendIntent(pendingIntent, intent); } - void sendIntentExit(PendingIntent pendingIntent) { + private void sendIntentExit(PendingIntent pendingIntent) { Intent intent = new Intent(); intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); sendIntent(pendingIntent, intent); } - void sendIntent(PendingIntent pendingIntent, Intent intent) { + private void sendIntent(PendingIntent pendingIntent, Intent intent) { try { mWakeLock.acquire(); pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); } catch (PendingIntent.CanceledException e) { - removeFence(pendingIntent); + removeFence(null, pendingIntent); mWakeLock.release(); } } - void updateProviderRequirements() { - synchronized (this) { - double minDistance = Double.MAX_VALUE; - for (GeofenceWrapper alert : mFences) { - if (alert.fence.getDistance() < minDistance) { - minDistance = alert.fence.getDistance(); - } + private void updateProviderRequirementsLocked() { + double minDistance = Double.MAX_VALUE; + for (GeofenceState state : mFences) { + if (state.getDistance() < minDistance) { + minDistance = state.getDistance(); } + } - if (minDistance == Double.MAX_VALUE) { - disableLocation(); - } else { - int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; - setLocationInterval(intervalMs); - } + if (minDistance == Double.MAX_VALUE) { + disableLocationLocked(); + } else { + int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; + requestLocationLocked(intervalMs); } } - void setLocationInterval(int intervalMs) { - mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, intervalMs, 0, this, + private void requestLocationLocked(int intervalMs) { + mLocationManager.requestLocationUpdates(new LocationRequest().setInterval(intervalMs), this, mLooper); } - void disableLocation() { + private void disableLocationLocked() { mLocationManager.removeUpdates(this); } @@ -231,11 +234,12 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish public void dump(PrintWriter pw) { pw.println(" Geofences:"); - for (GeofenceWrapper fenceWrapper : mFences) { + + for (GeofenceState state : mFences) { pw.append(" "); - pw.append(fenceWrapper.packageName); + pw.append(state.mPackageName); pw.append(" "); - pw.append(fenceWrapper.fence.toString()); + pw.append(state.mFence.toString()); pw.append("\n"); } } diff --git a/services/java/com/android/server/location/Geofence.java b/services/java/com/android/server/location/GeofenceState.java index f63607a..1fd737f 100644 --- a/services/java/com/android/server/location/Geofence.java +++ b/services/java/com/android/server/location/GeofenceState.java @@ -17,37 +17,42 @@ package com.android.server.location; +import android.app.PendingIntent; +import android.location.Geofence; import android.location.Location; /** - * Represents a simple circular GeoFence. + * Represents state associated with a geofence */ -public class Geofence { +public class GeofenceState { public final static int FLAG_ENTER = 0x01; public final static int FLAG_EXIT = 0x02; - static final int STATE_UNKNOWN = 0; - static final int STATE_INSIDE = 1; - static final int STATE_OUTSIDE = 2; + private static final int STATE_UNKNOWN = 0; + private static final int STATE_INSIDE = 1; + private static final int STATE_OUTSIDE = 2; - final double mLatitude; - final double mLongitude; - final float mRadius; - final Location mLocation; + public final Geofence mFence; + private final Location mLocation; + public final long mExpireAt; + public final String mPackageName; + public final PendingIntent mIntent; int mState; // current state double mDistance; // current distance to center of fence - public Geofence(double latitude, double longitude, float radius, Location prevLocation) { + public GeofenceState(Geofence fence, Location prevLocation, long expireAt, + String packageName, PendingIntent intent) { mState = STATE_UNKNOWN; - mLatitude = latitude; - mLongitude = longitude; - mRadius = radius; + mFence = fence; + mExpireAt = expireAt; + mPackageName = packageName; + mIntent = intent; mLocation = new Location(""); - mLocation.setLatitude(latitude); - mLocation.setLongitude(longitude); + mLocation.setLatitude(fence.getLatitude()); + mLocation.setLongitude(fence.getLongitude()); if (prevLocation != null) { processLocation(prevLocation); @@ -63,7 +68,7 @@ public class Geofence { int prevState = mState; //TODO: inside/outside detection could be made more rigorous - boolean inside = mDistance <= Math.max(mRadius, location.getAccuracy()); + boolean inside = mDistance <= Math.max(mFence.getRadius(), location.getAccuracy()); if (inside) { mState = STATE_INSIDE; } else { @@ -94,7 +99,6 @@ public class Geofence { default: state = "?"; } - return String.format("(%.4f, %.4f r=%.0f d=%.0f %s)", mLatitude, mLongitude, mRadius, - mDistance, state); + return String.format("%s d=%.0f %s", mFence.toString(), mDistance, state); } } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index bd7668b..3cd767d 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -29,6 +29,7 @@ import android.location.IGpsStatusProvider; import android.location.ILocationManager; import android.location.INetInitiatedListener; import android.location.Location; +import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.net.ConnectivityManager; @@ -54,17 +55,19 @@ import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; import android.util.Log; import android.util.NtpTrustedTime; -import android.util.SparseIntArray; - import com.android.internal.app.IBatteryStats; import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintWriter; import java.io.StringReader; import java.util.ArrayList; import java.util.Date; @@ -84,6 +87,10 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); + private static final ProviderProperties PROPERTIES = new ProviderProperties( + true, true, false, false, true, true, true, + Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); + // these need to match GpsPositionMode enum in gps.h private static final int GPS_POSITION_MODE_STANDALONE = 0; private static final int GPS_POSITION_MODE_MS_BASED = 1; @@ -150,14 +157,13 @@ public class GpsLocationProvider implements LocationProviderInterface { // Handler messages private static final int CHECK_LOCATION = 1; private static final int ENABLE = 2; - private static final int ENABLE_TRACKING = 3; + private static final int SET_REQUEST = 3; private static final int UPDATE_NETWORK_STATE = 4; private static final int INJECT_NTP_TIME = 5; private static final int DOWNLOAD_XTRA_DATA = 6; private static final int UPDATE_LOCATION = 7; private static final int ADD_LISTENER = 8; private static final int REMOVE_LISTENER = 9; - private static final int REQUEST_SINGLE_SHOT = 10; // Request setid private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; @@ -179,6 +185,18 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final String PROPERTIES_FILE = "/etc/gps.conf"; + /** simpler wrapper for ProviderRequest + Worksource */ + private static class GpsRequest { + public ProviderRequest request; + public WorkSource source; + public GpsRequest(ProviderRequest request, WorkSource source) { + this.request = request; + this.source = source; + } + } + + private Object mLock = new Object(); + private int mLocationFlags = LOCATION_INVALID; // current status @@ -198,9 +216,16 @@ public class GpsLocationProvider implements LocationProviderInterface { // Typical hot TTTF is ~5 seconds, so 10 seconds seems sane. private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000; - // true if we are enabled - private volatile boolean mEnabled; - + // how often to request NTP time, in milliseconds + // current setting 24 hours + private static final long NTP_INTERVAL = 24*60*60*1000; + // how long to wait if we have a network error in NTP or XTRA downloading + // current setting - 5 minutes + private static final long RETRY_INTERVAL = 5*60*1000; + + // true if we are enabled, protected by this + private boolean mEnabled; + // true if we have network connectivity private boolean mNetworkAvailable; @@ -217,16 +242,13 @@ public class GpsLocationProvider implements LocationProviderInterface { // true if GPS engine is on private boolean mEngineOn; - + // requested frequency of fixes, in milliseconds private int mFixInterval = 1000; // true if we started navigation private boolean mStarted; - // true if single shot request is in progress - private boolean mSingleShot; - // capabilities of the GPS engine private int mEngineCapabilities; @@ -236,7 +258,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // for calculating time to first fix private long mFixRequestTime = 0; // time to first fix for most recent session - private int mTTFF = 0; + private int mTimeToFirstFix = 0; // time we received our last fix private long mLastFixTime; @@ -251,7 +273,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private final Context mContext; private final NtpTrustedTime mNtpTime; - private final ILocationManager mLocationManager; + private final ILocationManager mILocationManager; private Location mLocation = new Location(LocationManager.GPS_PROVIDER); private Bundle mLocationExtras = new Bundle(); private ArrayList<Listener> mListeners = new ArrayList<Listener>(); @@ -267,17 +289,11 @@ public class GpsLocationProvider implements LocationProviderInterface { private int mAGpsDataConnectionState; private int mAGpsDataConnectionIpAddr; private final ConnectivityManager mConnMgr; - private final GpsNetInitiatedHandler mNIHandler; + private final GpsNetInitiatedHandler mNIHandler; // Wakelocks private final static String WAKELOCK_KEY = "GpsLocationProvider"; private final PowerManager.WakeLock mWakeLock; - // bitfield of pending messages to our Handler - // used only for messages that cannot have multiple instances queued - private int mPendingMessageBits; - // separate counter for ADD_LISTENER and REMOVE_LISTENER messages, - // which might have multiple instances queued - private int mPendingListenerMessages; // Alarms private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; @@ -287,22 +303,18 @@ public class GpsLocationProvider implements LocationProviderInterface { private final PendingIntent mTimeoutIntent; private final IBatteryStats mBatteryStats; - private final SparseIntArray mClientUids = new SparseIntArray(); - // how often to request NTP time, in milliseconds - // current setting 24 hours - private static final long NTP_INTERVAL = 24*60*60*1000; - // how long to wait if we have a network error in NTP or XTRA downloading - // current setting - 5 minutes - private static final long RETRY_INTERVAL = 5*60*1000; + // only modified on handler thread + private int[] mClientUids = new int[0]; private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { + @Override public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { if (listener == null) { throw new NullPointerException("listener is null in addGpsStatusListener"); } - synchronized(mListeners) { + synchronized (mListeners) { IBinder binder = listener.asBinder(); int size = mListeners.size(); for (int i = 0; i < size; i++) { @@ -319,12 +331,13 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + @Override public void removeGpsStatusListener(IGpsStatusListener listener) { if (listener == null) { throw new NullPointerException("listener is null in addGpsStatusListener"); } - synchronized(mListeners) { + synchronized (mListeners) { IBinder binder = listener.asBinder(); Listener l = null; int size = mListeners.size(); @@ -353,7 +366,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (action.equals(ALARM_WAKEUP)) { if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); - startNavigating(false); + startNavigating(); } else if (action.equals(ALARM_TIMEOUT)) { if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); hibernate(); @@ -361,6 +374,22 @@ public class GpsLocationProvider implements LocationProviderInterface { checkSmsSuplInit(intent); } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { checkWapSuplInit(intent); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + int networkState; + if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { + networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + } else { + networkState = LocationProvider.AVAILABLE; + } + + // retrieve NetworkInfo result for this UID + NetworkInfo info = + intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + ConnectivityManager connManager = (ConnectivityManager) + mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + info = connManager.getNetworkInfo(info.getType()); + + updateNetworkState(networkState, info); } } }; @@ -382,10 +411,10 @@ public class GpsLocationProvider implements LocationProviderInterface { return native_is_supported(); } - public GpsLocationProvider(Context context, ILocationManager locationManager) { + public GpsLocationProvider(Context context, ILocationManager ilocationManager) { mContext = context; mNtpTime = NtpTrustedTime.getInstance(context); - mLocationManager = locationManager; + mILocationManager = ilocationManager; mNIHandler = new GpsNetInitiatedHandler(context); mLocation.setExtras(mLocationExtras); @@ -393,7 +422,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // Create a wake lock PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mWakeLock.setReferenceCounted(false); + mWakeLock.setReferenceCounted(true); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); @@ -473,16 +502,14 @@ public class GpsLocationProvider implements LocationProviderInterface { /** * Returns the name of this provider. */ + @Override public String getName() { return LocationManager.GPS_PROVIDER; } - /** - * Returns true if the provider requires access to a - * data network (e.g., the Internet), false otherwise. - */ - public boolean requiresNetwork() { - return true; + @Override + public ProviderProperties getProperties() { + return PROPERTIES; } public void updateNetworkState(int state, NetworkInfo info) { @@ -516,7 +543,7 @@ public class GpsLocationProvider implements LocationProviderInterface { String apnName = info.getExtraInfo(); if (mNetworkAvailable) { if (apnName == null) { - /* Assign a dummy value in the case of C2K as otherwise we will have a runtime + /* Assign a dummy value in the case of C2K as otherwise we will have a runtime exception in the following call to native_agps_data_conn_open*/ apnName = "dummy-apn"; } @@ -613,18 +640,11 @@ public class GpsLocationProvider implements LocationProviderInterface { // try again later // since this is delayed and not urgent we do not hold a wake lock here mHandler.removeMessages(DOWNLOAD_XTRA_DATA); - mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL); + mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), + RETRY_INTERVAL); } } - /** - * This is called to inform us when another location provider returns a location. - * Someday we might use this for network location injection to aid the GPS - */ - public void updateLocation(Location location) { - sendMessage(UPDATE_LOCATION, 0, location); - } - private void handleUpdateLocation(Location location) { if (location.hasAccuracy()) { native_inject_location(location.getLatitude(), location.getLongitude(), @@ -633,107 +653,26 @@ public class GpsLocationProvider implements LocationProviderInterface { } /** - * Returns true if the provider requires access to a - * satellite-based positioning system (e.g., GPS), false - * otherwise. - */ - public boolean requiresSatellite() { - return true; - } - - /** - * Returns true if the provider requires access to an appropriate - * cellular network (e.g., to make use of cell tower IDs), false - * otherwise. - */ - public boolean requiresCell() { - return false; - } - - /** - * Returns true if the use of this provider may result in a - * monetary charge to the user, false if use is free. It is up to - * each provider to give accurate information. - */ - public boolean hasMonetaryCost() { - return false; - } - - /** - * Returns true if the provider is able to provide altitude - * information, false otherwise. A provider that reports altitude - * under most circumstances but may occassionally not report it - * should return true. - */ - public boolean supportsAltitude() { - return true; - } - - /** - * Returns true if the provider is able to provide speed - * information, false otherwise. A provider that reports speed - * under most circumstances but may occassionally not report it - * should return true. - */ - public boolean supportsSpeed() { - return true; - } - - /** - * Returns true if the provider is able to provide bearing - * information, false otherwise. A provider that reports bearing - * under most circumstances but may occassionally not report it - * should return true. - */ - public boolean supportsBearing() { - return true; - } - - /** - * Returns the power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants Criteria.POWER_REQUIREMENT_*. - */ - public int getPowerRequirement() { - return Criteria.POWER_HIGH; - } - - /** - * Returns true if this provider meets the given criteria, - * false otherwise. - */ - public boolean meetsCriteria(Criteria criteria) { - return (criteria.getPowerRequirement() != Criteria.POWER_LOW); - } - - /** - * Returns the horizontal accuracy of this provider - * - * @return the accuracy of location from this provider, as one - * of the constants Criteria.ACCURACY_*. - */ - public int getAccuracy() { - return Criteria.ACCURACY_FINE; - } - - /** * Enables this provider. When enabled, calls to getStatus() * must be handled. Hardware may be started up * when the provider is enabled. */ + @Override public void enable() { - synchronized (mHandler) { - sendMessage(ENABLE, 1, null); - } + sendMessage(ENABLE, 1, null); } private void handleEnable() { if (DEBUG) Log.d(TAG, "handleEnable"); - if (mEnabled) return; - mEnabled = native_init(); - if (mEnabled) { + synchronized (mLock) { + if (mEnabled) return; + mEnabled = true; + } + + boolean enabled = native_init(); + + if (enabled) { mSupportsXtra = native_supports_xtra(); if (mSuplServerHost != null) { native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); @@ -742,6 +681,9 @@ public class GpsLocationProvider implements LocationProviderInterface { native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); } } else { + synchronized (mLock) { + mEnabled = false; + } Log.w(TAG, "Failed to enable location provider"); } } @@ -751,27 +693,35 @@ public class GpsLocationProvider implements LocationProviderInterface { * need not be handled. Hardware may be shut * down while the provider is disabled. */ + @Override public void disable() { - synchronized (mHandler) { - sendMessage(ENABLE, 0, null); - } + sendMessage(ENABLE, 0, null); } private void handleDisable() { if (DEBUG) Log.d(TAG, "handleDisable"); - if (!mEnabled) return; - mEnabled = false; + synchronized (mLock) { + if (!mEnabled) return; + mEnabled = false; + } + stopNavigating(); + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); // do this before releasing wakelock native_cleanup(); } + @Override public boolean isEnabled() { - return mEnabled; + synchronized (mLock) { + return mEnabled; + } } + @Override public int getStatus(Bundle extras) { if (extras != null) { extras.putInt("satellites", mSvCount); @@ -788,93 +738,69 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + @Override public long getStatusUpdateTime() { return mStatusUpdateTime; } - public void enableLocationTracking(boolean enable) { - // FIXME - should set a flag here to avoid race conditions with single shot request - synchronized (mHandler) { - sendMessage(ENABLE_TRACKING, (enable ? 1 : 0), null); - } + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); } - private void handleEnableLocationTracking(boolean enable) { - if (enable) { - mTTFF = 0; - mLastFixTime = 0; - startNavigating(false); - } else { - if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { - mAlarmManager.cancel(mWakeupIntent); - mAlarmManager.cancel(mTimeoutIntent); - } - stopNavigating(); - } - } + private void handleSetRequest(ProviderRequest request, WorkSource source) { + if (DEBUG) Log.d(TAG, "setRequest " + request); - public boolean requestSingleShotFix() { - if (mStarted) { - // cannot do single shot if already navigating - return false; - } - synchronized (mHandler) { - mHandler.removeMessages(REQUEST_SINGLE_SHOT); - Message m = Message.obtain(mHandler, REQUEST_SINGLE_SHOT); - mHandler.sendMessage(m); - } - return true; - } - private void handleRequestSingleShot() { - mTTFF = 0; - mLastFixTime = 0; - startNavigating(true); - } - public void setMinTime(long minTime, WorkSource ws) { - if (DEBUG) Log.d(TAG, "setMinTime " + minTime); - - if (minTime >= 0) { - mFixInterval = (int)minTime; + if (request.reportLocation) { + // update client uids + int[] uids = new int[source.size()]; + for (int i=0; i < source.size(); i++) { + uids[i] = source.get(i); + } + updateClientUids(uids); + mFixInterval = (int) request.interval; + + // check for overflow + if (mFixInterval != request.interval) { + Log.w(TAG, "interval overflow: " + request.interval); + mFixInterval = Integer.MAX_VALUE; + } + + // apply request to GPS engine if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // change period if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, mFixInterval, 0, 0)) { Log.e(TAG, "set_position_mode failed in setMinTime()"); } + } else if (!mStarted) { + // start GPS + startNavigating(); } - } - } - - public String getInternalState() { - StringBuilder s = new StringBuilder(); - s.append(" mFixInterval=").append(mFixInterval).append("\n"); - s.append(" mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" ("); - if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED "); - if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB "); - if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA "); - if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT "); - if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME "); - s.append(")\n"); + } else { + updateClientUids(new int[0]); - s.append(native_get_internal_state()); - return s.toString(); + stopNavigating(); + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); + } } private final class Listener implements IBinder.DeathRecipient { final IGpsStatusListener mListener; - - int mSensors = 0; - + Listener(IGpsStatusListener listener) { mListener = listener; } - + + @Override public void binderDied() { if (DEBUG) Log.d(TAG, "GPS status listener died"); - synchronized(mListeners) { + synchronized (mListeners) { mListeners.remove(this); } if (mListener != null) { @@ -883,64 +809,47 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - public void addListener(int uid) { - synchronized (mWakeLock) { - mPendingListenerMessages++; - mWakeLock.acquire(); - Message m = Message.obtain(mHandler, ADD_LISTENER); - m.arg1 = uid; - mHandler.sendMessage(m); - } - } - - private void handleAddListener(int uid) { - synchronized(mListeners) { - if (mClientUids.indexOfKey(uid) >= 0) { - // Shouldn't be here -- already have this uid. - Log.w(TAG, "Duplicate add listener for uid " + uid); - return; + private void updateClientUids(int[] uids) { + // Find uid's that were not previously tracked + for (int uid1 : uids) { + boolean newUid = true; + for (int uid2 : mClientUids) { + if (uid1 == uid2) { + newUid = false; + break; + } } - mClientUids.put(uid, 0); - if (mNavigating) { + if (newUid) { try { - mBatteryStats.noteStartGps(uid); + mBatteryStats.noteStartGps(uid1); } catch (RemoteException e) { - Log.w(TAG, "RemoteException in addListener"); + Log.w(TAG, "RemoteException", e); } } } - } - - public void removeListener(int uid) { - synchronized (mWakeLock) { - mPendingListenerMessages++; - mWakeLock.acquire(); - Message m = Message.obtain(mHandler, REMOVE_LISTENER); - m.arg1 = uid; - mHandler.sendMessage(m); - } - } - private void handleRemoveListener(int uid) { - synchronized(mListeners) { - if (mClientUids.indexOfKey(uid) < 0) { - // Shouldn't be here -- don't have this uid. - Log.w(TAG, "Unneeded remove listener for uid " + uid); - return; + // Find uid'd that were tracked but have now disappeared + for (int uid1 : mClientUids) { + boolean oldUid = true; + for (int uid2 : uids) { + if (uid1 == uid2) { + oldUid = false; + break; + } } - mClientUids.delete(uid); - if (mNavigating) { + if (oldUid) { try { - mBatteryStats.noteStopGps(uid); + mBatteryStats.noteStopGps(uid1); } catch (RemoteException e) { - Log.w(TAG, "RemoteException in removeListener"); + Log.w(TAG, "RemoteException", e); } } } } + @Override public boolean sendExtraCommand(String command, Bundle extras) { - + long identity = Binder.clearCallingIdentity(); boolean result = false; @@ -957,7 +866,7 @@ public class GpsLocationProvider implements LocationProviderInterface { } else { Log.w(TAG, "sendExtraCommand: unknown command " + command); } - + Binder.restoreCallingIdentity(identity); return result; } @@ -992,18 +901,17 @@ public class GpsLocationProvider implements LocationProviderInterface { return false; } - private void startNavigating(boolean singleShot) { + private void startNavigating() { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); + mTimeToFirstFix = 0; + mLastFixTime = 0; mStarted = true; - mSingleShot = singleShot; mPositionMode = GPS_POSITION_MODE_STANDALONE; if (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { - if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { - mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; - } else if (hasCapability(GPS_CAPABILITY_MSB)) { + if (hasCapability(GPS_CAPABILITY_MSB)) { mPositionMode = GPS_POSITION_MODE_MS_BASED; } } @@ -1039,9 +947,8 @@ public class GpsLocationProvider implements LocationProviderInterface { if (DEBUG) Log.d(TAG, "stopNavigating"); if (mStarted) { mStarted = false; - mSingleShot = false; native_stop(); - mTTFF = 0; + mTimeToFirstFix = 0; mLastFixTime = 0; mLocationFlags = LOCATION_INVALID; @@ -1056,8 +963,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mAlarmManager.cancel(mTimeoutIntent); mAlarmManager.cancel(mWakeupIntent); long now = SystemClock.elapsedRealtime(); - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + mFixInterval, mWakeupIntent); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent); } private boolean hasCapability(int capability) { @@ -1105,7 +1011,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mLocation.setExtras(mLocationExtras); try { - mLocationManager.reportLocation(mLocation, false); + mILocationManager.reportLocation(mLocation, false); } catch (RemoteException e) { Log.e(TAG, "RemoteException calling reportLocation"); } @@ -1113,17 +1019,17 @@ public class GpsLocationProvider implements LocationProviderInterface { mLastFixTime = System.currentTimeMillis(); // report time to first fix - if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { - mTTFF = (int)(mLastFixTime - mFixRequestTime); - if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF); + if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime); + if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix); // notify status listeners - synchronized(mListeners) { + synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try { - listener.mListener.onFirstFix(mTTFF); + listener.mListener.onFirstFix(mTimeToFirstFix); } catch (RemoteException e) { Log.w(TAG, "RemoteException in stopNavigating"); mListeners.remove(listener); @@ -1134,9 +1040,6 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - if (mSingleShot) { - stopNavigating(); - } if (mStarted && mStatus != LocationProvider.AVAILABLE) { // we want to time out if we do not receive a fix // within the time out and we are requesting infrequent fixes @@ -1164,7 +1067,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private void reportStatus(int status) { if (DEBUG) Log.v(TAG, "reportStatus status: " + status); - synchronized(mListeners) { + synchronized (mListeners) { boolean wasNavigating = mNavigating; switch (status) { @@ -1202,20 +1105,6 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - try { - // update battery stats - for (int i=mClientUids.size() - 1; i >= 0; i--) { - int uid = mClientUids.keyAt(i); - if (mNavigating) { - mBatteryStats.noteStartGps(uid); - } else { - mBatteryStats.noteStopGps(uid); - } - } - } catch (RemoteException e) { - Log.w(TAG, "RemoteException in reportStatus"); - } - // send an intent to notify that the GPS has been enabled or disabled. Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating); @@ -1230,15 +1119,15 @@ public class GpsLocationProvider implements LocationProviderInterface { private void reportSvStatus() { int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); - - synchronized(mListeners) { + + synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try { - listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, - mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], - mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); + listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, + mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], + mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); } catch (RemoteException e) { Log.w(TAG, "RemoteException in reportSvInfo"); mListeners.remove(listener); @@ -1254,7 +1143,7 @@ public class GpsLocationProvider implements LocationProviderInterface { " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); for (int i = 0; i < svCount; i++) { Log.v(TAG, "sv: " + mSvs[i] + - " snr: " + (float)mSnrs[i]/10 + + " snr: " + mSnrs[i]/10 + " elev: " + mSvElevations[i] + " azimuth: " + mSvAzimuths[i] + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + @@ -1342,7 +1231,7 @@ public class GpsLocationProvider implements LocationProviderInterface { * called from native code to report NMEA data received */ private void reportNmea(long timestamp) { - synchronized(mListeners) { + synchronized (mListeners) { int size = mListeners.size(); if (size > 0) { // don't bother creating the String if we have no listeners @@ -1389,19 +1278,18 @@ public class GpsLocationProvider implements LocationProviderInterface { //============================================================= private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { // Sends a response for an NI reqeust to HAL. + @Override public boolean sendNiResponse(int notificationId, int userResponse) { // TODO Add Permission check - StringBuilder extrasBuf = new StringBuilder(); - if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + ", response: " + userResponse); native_send_ni_response(notificationId, userResponse); return true; } }; - + public INetInitiatedListener getNetInitiatedListener() { return mNetInitiatedListener; } @@ -1550,16 +1438,9 @@ public class GpsLocationProvider implements LocationProviderInterface { } private void sendMessage(int message, int arg, Object obj) { - // hold a wake lock while messages are pending - synchronized (mWakeLock) { - mPendingMessageBits |= (1 << message); - mWakeLock.acquire(); - mHandler.removeMessages(message); - Message m = Message.obtain(mHandler, message); - m.arg1 = arg; - m.obj = obj; - mHandler.sendMessage(m); - } + // hold a wake lock until this message is delivered + mWakeLock.acquire(); + mHandler.obtainMessage(message, arg, 1, obj).sendToTarget(); } private final class ProviderHandler extends Handler { @@ -1574,11 +1455,9 @@ public class GpsLocationProvider implements LocationProviderInterface { handleDisable(); } break; - case ENABLE_TRACKING: - handleEnableLocationTracking(msg.arg1 == 1); - break; - case REQUEST_SINGLE_SHOT: - handleRequestSingleShot(); + case SET_REQUEST: + GpsRequest gpsRequest = (GpsRequest) msg.obj; + handleSetRequest(gpsRequest.request, gpsRequest.source); break; case UPDATE_NETWORK_STATE: handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); @@ -1594,22 +1473,10 @@ public class GpsLocationProvider implements LocationProviderInterface { case UPDATE_LOCATION: handleUpdateLocation((Location)msg.obj); break; - case ADD_LISTENER: - handleAddListener(msg.arg1); - break; - case REMOVE_LISTENER: - handleRemoveListener(msg.arg1); - break; } - // release wake lock if no messages are pending - synchronized (mWakeLock) { - mPendingMessageBits &= ~(1 << message); - if (message == ADD_LISTENER || message == REMOVE_LISTENER) { - mPendingListenerMessages--; - } - if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) { - mWakeLock.release(); - } + if (msg.arg2 == 1) { + // wakelock was taken for this message, release it + mWakeLock.release(); } } }; @@ -1620,17 +1487,39 @@ public class GpsLocationProvider implements LocationProviderInterface { super("GpsLocationProvider"); } + @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); initialize(); Looper.prepare(); + + LocationManager locManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); mHandler = new ProviderHandler(); // signal when we are initialized and ready to go mInitializedLatch.countDown(); + locManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, 0, new NetworkLocationListener(), Looper.myLooper()); Looper.loop(); } } + private final class NetworkLocationListener implements LocationListener { + @Override + public void onLocationChanged(Location location) { + // this callback happens on mHandler looper + if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) { + handleUpdateLocation(location); + } + } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { } + @Override + public void onProviderEnabled(String provider) { } + @Override + public void onProviderDisabled(String provider) { } + } + private String getSelectedApn() { Uri uri = Uri.parse("content://telephony/carriers/preferapn"); String apn = null; @@ -1650,6 +1539,22 @@ public class GpsLocationProvider implements LocationProviderInterface { return apn; } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + StringBuilder s = new StringBuilder(); + s.append(" mFixInterval=").append(mFixInterval).append("\n"); + s.append(" mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" ("); + if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED "); + if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB "); + if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA "); + if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT "); + if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME "); + s.append(")\n"); + + s.append(native_get_internal_state()); + pw.append(s); + } + // for GPS SV statistics private static final int MAX_SVS = 32; private static final int EPHEMERIS_MASK = 0; diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java index 858a582..6f09232 100644 --- a/services/java/com/android/server/location/LocationProviderInterface.java +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -16,42 +16,33 @@ package com.android.server.location; -import android.location.Criteria; -import android.location.Location; -import android.net.NetworkInfo; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + + import android.os.Bundle; import android.os.WorkSource; /** * Location Manager's interface for location providers. - * - * {@hide} + * @hide */ public interface LocationProviderInterface { - String getName(); - boolean requiresNetwork(); - boolean requiresSatellite(); - boolean requiresCell(); - boolean hasMonetaryCost(); - boolean supportsAltitude(); - boolean supportsSpeed(); - boolean supportsBearing(); - int getPowerRequirement(); - boolean meetsCriteria(Criteria criteria); - int getAccuracy(); - boolean isEnabled(); - void enable(); - void disable(); - int getStatus(Bundle extras); - long getStatusUpdateTime(); - void enableLocationTracking(boolean enable); - /* returns false if single shot is not supported */ - boolean requestSingleShotFix(); - String getInternalState(); - void setMinTime(long minTime, WorkSource ws); - void updateNetworkState(int state, NetworkInfo info); - void updateLocation(Location location); - boolean sendExtraCommand(String command, Bundle extras); - void addListener(int uid); - void removeListener(int uid); + public String getName(); + + public void enable(); + public void disable(); + public boolean isEnabled(); + public void setRequest(ProviderRequest request, WorkSource source); + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + // --- deprecated (but still supported) --- + public ProviderProperties getProperties(); + public int getStatus(Bundle extras); + public long getStatusUpdateTime(); + public boolean sendExtraCommand(String command, Bundle extras); } diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java index a227ab6..7faf72c 100644 --- a/services/java/com/android/server/location/LocationProviderProxy.java +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -16,458 +16,272 @@ package com.android.server.location; -import android.content.ComponentName; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.location.Criteria; -import android.location.ILocationProvider; -import android.location.Location; -import android.net.NetworkInfo; +import android.location.LocationProvider; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; -import com.android.internal.location.DummyLocationProvider; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ProviderRequest; +import com.android.server.LocationManagerService; +import com.android.server.ServiceWatcher; /** - * A class for proxying location providers implemented as services. - * - * {@hide} + * Proxy for ILocationProvider implementations. */ public class LocationProviderProxy implements LocationProviderInterface { - private static final String TAG = "LocationProviderProxy"; - - public static final String SERVICE_ACTION = - "com.android.location.service.NetworkLocationProvider"; + private static final boolean D = LocationManagerService.D; private final Context mContext; private final String mName; - private final Intent mIntent; - private final Handler mHandler; - private final Object mMutex = new Object(); // synchronizes access to non-final members - private Connection mServiceConnection; // never null after ctor + private final ServiceWatcher mServiceWatcher; + + private Object mLock = new Object(); - // cached values set by the location manager - private boolean mLocationTracking = false; + // cached values set by the location manager, synchronized on mLock + private ProviderProperties mProperties; private boolean mEnabled = false; - private long mMinTime = -1; - private WorkSource mMinTimeSource = new WorkSource(); - private int mNetworkState; - private NetworkInfo mNetworkInfo; - - // constructor for proxying location providers implemented in a separate service - public LocationProviderProxy(Context context, String name, String packageName, - Handler handler) { + private ProviderRequest mRequest = null; + private WorkSource mWorksource = new WorkSource(); + + public static LocationProviderProxy createAndBind(Context context, String name, String action, + List<String> initialPackageNames, Handler handler) { + LocationProviderProxy proxy = new LocationProviderProxy(context, name, action, + initialPackageNames, handler); + if (proxy.bind()) { + return proxy; + } else { + return null; + } + } + + private LocationProviderProxy(Context context, String name, String action, + List<String> initialPackageNames, Handler handler) { mContext = context; mName = name; - mIntent = new Intent(SERVICE_ACTION); - mHandler = handler; - reconnect(packageName); + mServiceWatcher = new ServiceWatcher(mContext, TAG, action, initialPackageNames, + mNewServiceWork, handler); } - /** Bind to service. Will reconnect if already connected */ - public void reconnect(String packageName) { - synchronized (mMutex) { - if (mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - } - mServiceConnection = new Connection(); - mIntent.setPackage(packageName); - mContext.bindService(mIntent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | - Context.BIND_ALLOW_OOM_MANAGEMENT); - } + private boolean bind () { + return mServiceWatcher.start(); } - private class Connection implements ServiceConnection, Runnable { - - private ILocationProvider mProvider; - - // for caching requiresNetwork, requiresSatellite, etc. - private DummyLocationProvider mCachedAttributes; // synchronized by mMutex + private ILocationProvider getService() { + return ILocationProvider.Stub.asInterface(mServiceWatcher.getBinder()); + } - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (this) { - mProvider = ILocationProvider.Stub.asInterface(service); - if (mProvider != null) { - mHandler.post(this); - } - } - } + public String getConnectedPackageName() { + return mServiceWatcher.getBestPackageName(); + } - public void onServiceDisconnected(ComponentName className) { - synchronized (this) { - mProvider = null; + /** + * Work to apply current state to a newly connected provider. + * Remember we can switch the service that implements a providers + * at run-time, so need to apply current state. + */ + private Runnable mNewServiceWork = new Runnable() { + @Override + public void run() { + if (D) Log.d(TAG, "applying state to connected service"); + + boolean enabled; + ProviderProperties properties = null; + ProviderRequest request; + WorkSource source; + ILocationProvider service; + synchronized (mLock) { + enabled = mEnabled; + request = mRequest; + source = mWorksource; + service = getService(); } - } - public synchronized ILocationProvider getProvider() { - return mProvider; - } - - public synchronized DummyLocationProvider getCachedAttributes() { - return mCachedAttributes; - } + if (service == null) return; - public void run() { - synchronized (mMutex) { - if (mServiceConnection != this) { - // This ServiceConnection no longer the one we want to bind to. - return; - } - ILocationProvider provider = getProvider(); - if (provider == null) { - return; + try { + // load properties from provider + properties = service.getProperties(); + if (properties == null) { + Log.e(TAG, mServiceWatcher.getBestPackageName() + + " has invalid locatino provider properties"); } - // resend previous values from the location manager if the service has restarted - try { - if (mEnabled) { - provider.enable(); - } - if (mLocationTracking) { - provider.enableLocationTracking(true); + // apply current state to new service + if (enabled) { + service.enable(); + if (request != null) { + service.setRequest(request, source); } - if (mMinTime >= 0) { - provider.setMinTime(mMinTime, mMinTimeSource); - } - if (mNetworkInfo != null) { - provider.updateNetworkState(mNetworkState, mNetworkInfo); - } - } catch (RemoteException e) { } + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); + } - // init cache of parameters - if (mCachedAttributes == null) { - try { - mCachedAttributes = new DummyLocationProvider(mName, null); - mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); - mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); - mCachedAttributes.setRequiresCell(provider.requiresCell()); - mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost()); - mCachedAttributes.setSupportsAltitude(provider.supportsAltitude()); - mCachedAttributes.setSupportsSpeed(provider.supportsSpeed()); - mCachedAttributes.setSupportsBearing(provider.supportsBearing()); - mCachedAttributes.setPowerRequirement(provider.getPowerRequirement()); - mCachedAttributes.setAccuracy(provider.getAccuracy()); - } catch (RemoteException e) { - mCachedAttributes = null; - } - } + synchronized (mLock) { + mProperties = properties; } } }; + @Override public String getName() { return mName; } - private DummyLocationProvider getCachedAttributes() { - synchronized (mMutex) { - return mServiceConnection.getCachedAttributes(); - } - } - - public boolean requiresNetwork() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresNetwork(); - } else { - return false; - } - } - - public boolean requiresSatellite() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresSatellite(); - } else { - return false; - } - } - - public boolean requiresCell() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresCell(); - } else { - return false; - } - } - - public boolean hasMonetaryCost() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.hasMonetaryCost(); - } else { - return false; - } - } - - public boolean supportsAltitude() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsAltitude(); - } else { - return false; - } - } - - public boolean supportsSpeed() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsSpeed(); - } else { - return false; - } - } - - public boolean supportsBearing() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsBearing(); - } else { - return false; - } - } - - public int getPowerRequirement() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.getPowerRequirement(); - } else { - return -1; + @Override + public ProviderProperties getProperties() { + synchronized (mLock) { + return mProperties; } } - public int getAccuracy() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.getAccuracy(); - } else { - return -1; - } - } - - public boolean meetsCriteria(Criteria criteria) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - return provider.meetsCriteria(criteria); - } catch (RemoteException e) { - } - } - } - // default implementation if we lost connection to the provider - if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && - (criteria.getAccuracy() < getAccuracy())) { - return false; - } - int criteriaPower = criteria.getPowerRequirement(); - if ((criteriaPower != Criteria.NO_REQUIREMENT) && - (criteriaPower < getPowerRequirement())) { - return false; - } - if (criteria.isAltitudeRequired() && !supportsAltitude()) { - return false; - } - if (criteria.isSpeedRequired() && !supportsSpeed()) { - return false; - } - if (criteria.isBearingRequired() && !supportsBearing()) { - return false; - } - return true; - } - + @Override public void enable() { - synchronized (mMutex) { + synchronized (mLock) { mEnabled = true; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.enable(); - } catch (RemoteException e) { - } - } } - } + ILocationProvider service = getService(); + if (service == null) return; - public void disable() { - synchronized (mMutex) { - mEnabled = false; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.disable(); - } catch (RemoteException e) { - } - } + try { + service.enable(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public boolean isEnabled() { - synchronized (mMutex) { - return mEnabled; + @Override + public void disable() { + synchronized (mLock) { + mEnabled = false; } - } + ILocationProvider service = getService(); + if (service == null) return; - public int getStatus(Bundle extras) { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); + try { + service.disable(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } - if (provider != null) { - try { - return provider.getStatus(extras); - } catch (RemoteException e) { - } - } - return 0; } - public long getStatusUpdateTime() { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } - if (provider != null) { - try { - return provider.getStatusUpdateTime(); - } catch (RemoteException e) { - } - } - return 0; - } - - public String getInternalState() { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } - if (provider != null) { - try { - return provider.getInternalState(); - } catch (RemoteException e) { - Log.e(TAG, "getInternalState failed", e); - } + @Override + public boolean isEnabled() { + synchronized (mLock) { + return mEnabled; } - return null; } - public boolean isLocationTracking() { - synchronized (mMutex) { - return mLocationTracking; + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + synchronized (mLock) { + mRequest = request; + mWorksource = source; } - } + ILocationProvider service = getService(); + if (service == null) return; - public void enableLocationTracking(boolean enable) { - synchronized (mMutex) { - mLocationTracking = enable; - if (!enable) { - mMinTime = -1; - mMinTimeSource.clear(); - } - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.enableLocationTracking(enable); - } catch (RemoteException e) { - } - } + try { + service.setRequest(request, source); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public boolean requestSingleShotFix() { - return false; - } - - public long getMinTime() { - synchronized (mMutex) { - return mMinTime; + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.append("REMOTE SERVICE"); + pw.append(" name=").append(mName); + pw.append(" pkg=").append(mServiceWatcher.getBestPackageName()); + pw.append(" version=").append("" + mServiceWatcher.getBestVersion()); + pw.append('\n'); + + ILocationProvider service = getService(); + if (service == null) { + pw.println("service down (null)"); + return; + } + pw.flush(); + + try { + service.asBinder().dump(fd, args); + } catch (RemoteException e) { + pw.println("service down (RemoteException)"); + Log.w(TAG, e); + } catch (Exception e) { + pw.println("service down (Exception)"); + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public void setMinTime(long minTime, WorkSource ws) { - synchronized (mMutex) { - mMinTime = minTime; - mMinTimeSource.set(ws); - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.setMinTime(minTime, ws); - } catch (RemoteException e) { - } - } - } + @Override + public int getStatus(Bundle extras) { + ILocationProvider service = getService(); + if (service == null) return LocationProvider.TEMPORARILY_UNAVAILABLE; + + try { + return service.getStatus(extras); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); + } + return LocationProvider.TEMPORARILY_UNAVAILABLE; } - public void updateNetworkState(int state, NetworkInfo info) { - synchronized (mMutex) { - mNetworkState = state; - mNetworkInfo = info; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.updateNetworkState(state, info); - } catch (RemoteException e) { - } - } - } - } + @Override + public long getStatusUpdateTime() { + ILocationProvider service = getService(); + if (service == null) return 0; - public void updateLocation(Location location) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.updateLocation(location); - } catch (RemoteException e) { - } - } + try { + return service.getStatusUpdateTime(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } + return 0; } + @Override public boolean sendExtraCommand(String command, Bundle extras) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - return provider.sendExtraCommand(command, extras); - } catch (RemoteException e) { - } - } - } - return false; - } + ILocationProvider service = getService(); + if (service == null) return false; - public void addListener(int uid) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.addListener(uid); - } catch (RemoteException e) { - } - } - } - } - - public void removeListener(int uid) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.removeListener(uid); - } catch (RemoteException e) { - } - } + try { + return service.sendExtraCommand(command, extras); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } + return false; } -} + } diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java index 09d799f..36c43ff 100644 --- a/services/java/com/android/server/location/MockProvider.java +++ b/services/java/com/android/server/location/MockProvider.java @@ -20,15 +20,19 @@ import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationProvider; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; import android.util.PrintWriterPrinter; + +import java.io.FileDescriptor; import java.io.PrintWriter; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + /** * A mock location provider used by LocationManagerService to implement test providers. * @@ -36,60 +40,56 @@ import java.io.PrintWriter; */ public class MockProvider implements LocationProviderInterface { private final String mName; + private final ProviderProperties mProperties; private final ILocationManager mLocationManager; - private final boolean mRequiresNetwork; - private final boolean mRequiresSatellite; - private final boolean mRequiresCell; - private final boolean mHasMonetaryCost; - private final boolean mSupportsAltitude; - private final boolean mSupportsSpeed; - private final boolean mSupportsBearing; - private final int mPowerRequirement; - private final int mAccuracy; + private final Location mLocation; + private final Bundle mExtras = new Bundle(); + private int mStatus; private long mStatusUpdateTime; - private final Bundle mExtras = new Bundle(); private boolean mHasLocation; private boolean mHasStatus; private boolean mEnabled; private static final String TAG = "MockProvider"; - public MockProvider(String name, ILocationManager locationManager, - boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + public MockProvider(String name, ILocationManager locationManager, + ProviderProperties properties) { + if (properties == null) throw new NullPointerException("properties is null"); + mName = name; mLocationManager = locationManager; - mRequiresNetwork = requiresNetwork; - mRequiresSatellite = requiresSatellite; - mRequiresCell = requiresCell; - mHasMonetaryCost = hasMonetaryCost; - mSupportsAltitude = supportsAltitude; - mSupportsBearing = supportsBearing; - mSupportsSpeed = supportsSpeed; - mPowerRequirement = powerRequirement; - mAccuracy = accuracy; + mProperties = properties; mLocation = new Location(name); } + @Override public String getName() { return mName; } + @Override + public ProviderProperties getProperties() { + return mProperties; + } + + @Override public void disable() { mEnabled = false; } + @Override public void enable() { mEnabled = true; } + @Override public boolean isEnabled() { return mEnabled; } + @Override public int getStatus(Bundle extras) { if (mHasStatus) { extras.clear(); @@ -100,75 +100,20 @@ public class MockProvider implements LocationProviderInterface { } } + @Override public long getStatusUpdateTime() { return mStatusUpdateTime; } - public int getAccuracy() { - return mAccuracy; - } - - public int getPowerRequirement() { - return mPowerRequirement; - } - - public boolean hasMonetaryCost() { - return mHasMonetaryCost; - } - - public boolean requiresCell() { - return mRequiresCell; - } - - public boolean requiresNetwork() { - return mRequiresNetwork; - } - - public boolean requiresSatellite() { - return mRequiresSatellite; - } - - public boolean supportsAltitude() { - return mSupportsAltitude; - } - - public boolean supportsBearing() { - return mSupportsBearing; - } - - public boolean supportsSpeed() { - return mSupportsSpeed; - } - - public boolean meetsCriteria(Criteria criteria) { - if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && - (criteria.getAccuracy() < mAccuracy)) { - return false; - } - int criteriaPower = criteria.getPowerRequirement(); - if ((criteriaPower != Criteria.NO_REQUIREMENT) && - (criteriaPower < mPowerRequirement)) { - return false; - } - if (criteria.isAltitudeRequired() && !mSupportsAltitude) { - return false; - } - if (criteria.isSpeedRequired() && !mSupportsSpeed) { - return false; - } - if (criteria.isBearingRequired() && !mSupportsBearing) { - return false; - } - return true; - } - public void setLocation(Location l) { mLocation.set(l); mHasLocation = true; - try { - mLocationManager.reportLocation(mLocation, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocation"); + if (mEnabled) { + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } } } @@ -191,34 +136,9 @@ public class MockProvider implements LocationProviderInterface { mStatusUpdateTime = 0; } - public String getInternalState() { - return null; - } - - public void enableLocationTracking(boolean enable) { - } - - public boolean requestSingleShotFix() { - return false; - } - - public void setMinTime(long minTime, WorkSource ws) { - } - - public void updateNetworkState(int state, NetworkInfo info) { - } - - public void updateLocation(Location location) { - } - - public boolean sendExtraCommand(String command, Bundle extras) { - return false; - } - - public void addListener(int uid) { - } - - public void removeListener(int uid) { + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + dump(pw, ""); } public void dump(PrintWriter pw, String prefix) { @@ -231,4 +151,12 @@ public class MockProvider implements LocationProviderInterface { pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime); pw.println(prefix + "mExtras=" + mExtras); } + + @Override + public void setRequest(ProviderRequest request, WorkSource source) { } + + @Override + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } } diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java index ea0d1b0..0ce21b7 100644 --- a/services/java/com/android/server/location/PassiveProvider.java +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -16,17 +16,23 @@ package com.android.server.location; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; + /** * A passive location provider reports locations received from other providers * for clients that want to listen passively without actually triggering @@ -35,103 +41,63 @@ import android.util.Log; * {@hide} */ public class PassiveProvider implements LocationProviderInterface { - private static final String TAG = "PassiveProvider"; + private static final ProviderProperties PROPERTIES = new ProviderProperties( + false, false, false, false, false, false, false, + Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + private final ILocationManager mLocationManager; - private boolean mTracking; + private boolean mReportLocation; public PassiveProvider(ILocationManager locationManager) { mLocationManager = locationManager; } + @Override public String getName() { return LocationManager.PASSIVE_PROVIDER; } - public boolean requiresNetwork() { - return false; - } - - public boolean requiresSatellite() { - return false; - } - - public boolean requiresCell() { - return false; - } - - public boolean hasMonetaryCost() { - return false; - } - - public boolean supportsAltitude() { - return false; - } - - public boolean supportsSpeed() { - return false; - } - - public boolean supportsBearing() { - return false; - } - - public int getPowerRequirement() { - return -1; - } - - public boolean meetsCriteria(Criteria criteria) { - // We do not want to match the special passive provider based on criteria. - return false; - } - - public int getAccuracy() { - return -1; + @Override + public ProviderProperties getProperties() { + return PROPERTIES; } + @Override public boolean isEnabled() { return true; } + @Override public void enable() { } + @Override public void disable() { } + @Override public int getStatus(Bundle extras) { - if (mTracking) { + if (mReportLocation) { return LocationProvider.AVAILABLE; } else { return LocationProvider.TEMPORARILY_UNAVAILABLE; } } + @Override public long getStatusUpdateTime() { return -1; } - public String getInternalState() { - return null; - } - - public void enableLocationTracking(boolean enable) { - mTracking = enable; - } - - public boolean requestSingleShotFix() { - return false; - } - - public void setMinTime(long minTime, WorkSource ws) { - } - - public void updateNetworkState(int state, NetworkInfo info) { + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + mReportLocation = request.reportLocation; } public void updateLocation(Location location) { - if (mTracking) { + if (mReportLocation) { try { // pass the location back to the location manager mLocationManager.reportLocation(location, true); @@ -141,13 +107,13 @@ public class PassiveProvider implements LocationProviderInterface { } } + @Override public boolean sendExtraCommand(String command, Bundle extras) { return false; } - public void addListener(int uid) { - } - - public void removeListener(int uid) { + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("mReportLocaiton=" + mReportLocation); } } |