Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b711d57c authored by Victoria Lease's avatar Victoria Lease
Browse files

Multiuser love for LocationManager

LocationManagerService now keeps track of the current user ID and
denies location requests made by all but the foreground user.

Additionally, location settings are now user-specific, rather than
global to the device. Location provider services now run as specific
users, and when the device's foreground user changes, we rebind to
appropriately-owned providers.

Bug: 6926385
Bug: 7247203
Change-Id: I346074959e96e52bcc77eeb188dffe322b690879
parent bb5f014a
Loading
Loading
Loading
Loading
+29 −2
Original line number Diff line number Diff line
@@ -4052,7 +4052,20 @@ public final class Settings {
         * @return true if the provider is enabled
         */
        public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
            String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
            return isLocationProviderEnabledForUser(cr, provider, UserHandle.myUserId());
        }

        /**
         * Helper method for determining if a location provider is enabled.
         * @param cr the content resolver to use
         * @param provider the location provider to query
         * @param userId the userId to query
         * @return true if the provider is enabled
         * @hide
         */
        public static final boolean isLocationProviderEnabledForUser(ContentResolver cr, String provider, int userId) {
            String allowedProviders = Settings.Secure.getStringForUser(cr,
                    LOCATION_PROVIDERS_ALLOWED, userId);
            return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
        }

@@ -4064,6 +4077,19 @@ public final class Settings {
         */
        public static final void setLocationProviderEnabled(ContentResolver cr,
                String provider, boolean enabled) {
            setLocationProviderEnabledForUser(cr, provider, enabled, UserHandle.myUserId());
        }

        /**
         * Thread-safe method for enabling or disabling a single location provider.
         * @param cr the content resolver to use
         * @param provider the location provider to enable or disable
         * @param enabled true if the provider should be enabled
         * @param userId the userId for which to enable/disable providers
         * @hide
         */
        public static final void setLocationProviderEnabledForUser(ContentResolver cr,
                String provider, boolean enabled, int userId) {
            // to ensure thread safety, we write the provider name with a '+' or '-'
            // and let the SettingsProvider handle it rather than reading and modifying
            // the list of enabled providers.
@@ -4072,7 +4098,8 @@ public final class Settings {
            } else {
                provider = "-" + provider;
            }
            putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
            putStringForUser(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider,
                    userId);
        }
    }

+111 −73
Original line number Diff line number Diff line
@@ -226,7 +226,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                   updateProvidersLocked();
               }
            }
        });
        }, UserHandle.USER_ALL);
        mPackageMonitor.register(mContext, Looper.myLooper(), true);

        // listen for user change
@@ -289,7 +289,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                mContext,
                LocationManager.NETWORK_PROVIDER,
                NETWORK_LOCATION_SERVICE_ACTION,
                providerPackageNames, mLocationHandler);
                providerPackageNames, mLocationHandler, mCurrentUserId);
        if (networkProvider != null) {
            mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider);
            mProxyProviders.add(networkProvider);
@@ -303,7 +303,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                mContext,
                LocationManager.FUSED_PROVIDER,
                FUSED_LOCATION_SERVICE_ACTION,
                providerPackageNames, mLocationHandler);
                providerPackageNames, mLocationHandler, mCurrentUserId);
        if (fusedLocationProvider != null) {
            addProviderLocked(fusedLocationProvider);
            mProxyProviders.add(fusedLocationProvider);
@@ -314,7 +314,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        }

        // bind to geocoder provider
        mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames);
        mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames,
                mCurrentUserId);
        if (mGeocodeProvider == null) {
            Slog.e(TAG,  "no geocoder provider found");
        }
@@ -326,11 +327,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
     */
    private void switchUser(int userId) {
        mBlacklist.switchUser(userId);
        //Log.d("LocationManagerService", "switchUser(" + mCurrentUserId + " -> " + userId + ")"); // TODO: remove this
        synchronized (mLock) {
            // TODO: inform previous user's Receivers that they will no longer receive updates
            mLastLocation.clear();
            for (LocationProviderInterface p : mProviders) {
                updateProviderListenersLocked(p.getName(), false, mCurrentUserId);
                p.switchUser(userId);
            }
            mCurrentUserId = userId;
            // TODO: inform new user's Receivers that they are back on the update train
            updateProvidersLocked();
        }
    }

@@ -587,7 +591,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
    }


    private boolean isAllowedBySettingsLocked(String provider) {
    private boolean isAllowedBySettingsLocked(String provider, int userId) {
        if (userId != mCurrentUserId) {
            return false;
        }
        if (mEnabledProviders.contains(provider)) {
            return true;
        }
@@ -597,7 +604,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        // Use system settings
        ContentResolver resolver = mContext.getContentResolver();

        return Settings.Secure.isLocationProviderEnabled(resolver, provider);
        return Settings.Secure.isLocationProviderEnabledForUser(resolver, provider, mCurrentUserId);
    }

    /**
@@ -695,6 +702,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
    @Override
    public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
        ArrayList<String> out;
        int callingUserId = UserHandle.getCallingUserId();
        long identity = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                out = new ArrayList<String>(mProviders.size());
                for (LocationProviderInterface provider : mProviders) {
@@ -703,7 +713,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                        continue;
                    }
                    if (isAllowedProviderSafe(name)) {
                    if (enabledOnly && !isAllowedBySettingsLocked(name)) {
                        if (enabledOnly && !isAllowedBySettingsLocked(name, callingUserId)) {
                            continue;
                        }
                        if (criteria != null && !LocationProvider.propertiesMeetCriteria(
@@ -714,6 +724,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                    }
                }
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        if (D) Log.d(TAG, "getProviders()=" + out);
        return out;
@@ -778,12 +791,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
            LocationProviderInterface p = mProviders.get(i);
            boolean isEnabled = p.isEnabled();
            String name = p.getName();
            boolean shouldBeEnabled = isAllowedBySettingsLocked(name);
            boolean shouldBeEnabled = isAllowedBySettingsLocked(name, mCurrentUserId);
            if (isEnabled && !shouldBeEnabled) {
                updateProviderListenersLocked(name, false);
                updateProviderListenersLocked(name, false, mCurrentUserId);
                changesMade = true;
            } else if (!isEnabled && shouldBeEnabled) {
                updateProviderListenersLocked(name, true);
                updateProviderListenersLocked(name, true, mCurrentUserId);
                changesMade = true;
            }
        }
@@ -793,7 +806,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        }
    }

    private void updateProviderListenersLocked(String provider, boolean enabled) {
    private void updateProviderListenersLocked(String provider, boolean enabled, int userId) {
        int listeners = 0;

        LocationProviderInterface p = mProvidersByName.get(provider);
@@ -806,6 +819,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
            final int N = records.size();
            for (int i = 0; i < N; i++) {
                UpdateRecord record = records.get(i);
                if (UserHandle.getUserId(record.mReceiver.mUid) == userId) {
                    // Sends a notification message to the receiver
                    if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
                        if (deadReceivers == null) {
@@ -816,6 +830,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                    listeners++;
                }
            }
        }

        if (deadReceivers != null) {
            for (int i = deadReceivers.size() - 1; i >= 0; i--) {
@@ -843,14 +858,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run

        if (records != null) {
            for (UpdateRecord record : records) {
                if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
                    LocationRequest locationRequest = record.mRequest;

                    providerRequest.locationRequests.add(locationRequest);
                    if (locationRequest.getInterval() < providerRequest.interval) {
                        providerRequest.reportLocation = true;
                        providerRequest.interval = locationRequest.getInterval();
                    }
                }
            }

            if (providerRequest.reportLocation) {
                // calculate who to blame for power
@@ -860,6 +876,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                // under that threshold.
                long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
                for (UpdateRecord record : records) {
                    if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
                        LocationRequest locationRequest = record.mRequest;
                        if (locationRequest.getInterval() <= thresholdInterval) {
                            worksource.add(record.mReceiver.mUid);
@@ -867,6 +884,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                    }
                }
            }
        }

        if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
        p.setRequest(providerRequest, worksource);
@@ -1084,7 +1102,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
            oldRecord.disposeLocked(false);
        }

        boolean isProviderEnabled = isAllowedBySettingsLocked(name);
        boolean isProviderEnabled = isAllowedBySettingsLocked(name, UserHandle.getUserId(uid));
        if (isProviderEnabled) {
            applyRequirementsLocked(name);
        } else {
@@ -1141,7 +1159,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        // update provider
        for (String provider : providers) {
            // If provider is already disabled, don't need to do anything
            if (!isAllowedBySettingsLocked(provider)) {
            if (!isAllowedBySettingsLocked(provider, mCurrentUserId)) {
                continue;
            }

@@ -1156,6 +1174,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        String perm = checkPermissionAndRequest(request);
        checkPackageName(packageName);

        long identity = Binder.clearCallingIdentity();
        try {
            if (mBlacklist.isBlacklisted(packageName)) {
                if (D) Log.d(TAG, "not returning last loc for blacklisted app: " +
                        packageName);
@@ -1170,7 +1190,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                LocationProviderInterface provider = mProvidersByName.get(name);
                if (provider == null) return null;

            if (!isAllowedBySettingsLocked(name)) return null;
                if (!isAllowedBySettingsLocked(name, mCurrentUserId)) return null;

                Location location = mLastLocation.get(name);
                if (location == null) {
@@ -1186,6 +1206,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                }
            }
            return null;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @Override
@@ -1321,11 +1344,16 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                    "\" provider requires ACCESS_FINE_LOCATION permission");
        }

        long identity = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                LocationProviderInterface p = mProvidersByName.get(provider);
                if (p == null) return false;

            return isAllowedBySettingsLocked(provider);
                return isAllowedBySettingsLocked(provider, mCurrentUserId);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

@@ -1458,6 +1486,16 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
            Receiver receiver = r.mReceiver;
            boolean receiverDead = false;

            int receiverUserId = UserHandle.getUserId(receiver.mUid);
            if (receiverUserId != mCurrentUserId) {
                if (D) {
                    Log.d(TAG, "skipping loc update for background user " + receiverUserId +
                            " (current user: " + mCurrentUserId + ", app: " +
                            receiver.mPackageName + ")");
                }
                continue;
            }

            if (mBlacklist.isBlacklisted(receiver.mPackageName)) {
                if (D) Log.d(TAG, "skipping loc update for blacklisted app: " +
                        receiver.mPackageName);
@@ -1548,7 +1586,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
        }

        synchronized (mLock) {
            if (isAllowedBySettingsLocked(provider)) {
            if (isAllowedBySettingsLocked(provider, mCurrentUserId)) {
                handleLocationChangedLocked(location, passive);
            }
        }
+50 −35
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.Log;

import com.android.internal.content.PackageMonitor;
@@ -58,15 +59,17 @@ public class ServiceWatcher implements ServiceConnection {
    private IBinder mBinder;   // connected service
    private String mPackageName;  // current best package
    private int mVersion;  // current best version
    private int mCurrentUserId;

    public ServiceWatcher(Context context, String logTag, String action,
            List<String> initialPackageNames, Runnable newServiceWork, Handler handler) {
            List<String> initialPackageNames, Runnable newServiceWork, Handler handler, int userId) {
        mContext = context;
        mTag = logTag;
        mAction = action;
        mPm = mContext.getPackageManager();
        mNewServiceWork = newServiceWork;
        mHandler = handler;
        mCurrentUserId = userId;

        mSignatureSets = new ArrayList<HashSet<Signature>>();
        for (int i=0; i < initialPackageNames.size(); i++) {
@@ -85,9 +88,11 @@ public class ServiceWatcher implements ServiceConnection {
    }

    public boolean start() {
        if (!bindBestPackage(null)) return false;
        synchronized (mLock) {
            if (!bindBestPackageLocked(null)) return false;
        }

        mPackageMonitor.register(mContext, null, true);
        mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
        return true;
    }

@@ -98,13 +103,13 @@ public class ServiceWatcher implements ServiceConnection {
     * is null.
     * Return true if a new package was found to bind to.
     */
    private boolean bindBestPackage(String justCheckThisPackage) {
    private boolean bindBestPackageLocked(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);
        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(new Intent(mAction),
                PackageManager.GET_META_DATA, mCurrentUserId);
        int bestVersion = Integer.MIN_VALUE;
        String bestPackage = null;
        for (ResolveInfo rInfo : rInfos) {
@@ -141,36 +146,32 @@ public class ServiceWatcher implements ServiceConnection {
                (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage)));

        if (bestPackage != null) {
            bindToPackage(bestPackage, bestVersion);
            bindToPackageLocked(bestPackage, bestVersion);
            return true;
        }
        return false;
    }

    private void unbind() {
    private void unbindLocked() {
        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();
    private void bindToPackageLocked(String packageName, int version) {
        unbindLocked();
        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 | Context.BIND_NOT_VISIBLE);
                | Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_NOT_VISIBLE, mCurrentUserId);
    }

    private boolean isSignatureMatch(Signature[] signatures) {
@@ -197,31 +198,37 @@ public class ServiceWatcher implements ServiceConnection {
         */
        @Override
        public void onPackageUpdateFinished(String packageName, int uid) {
            synchronized (mLock) {
                if (packageName.equals(mPackageName)) {
                    // package updated, make sure to rebind
                unbind();
                    unbindLocked();
                }
                // check the updated package in case it is better
            bindBestPackage(packageName);
                bindBestPackageLocked(packageName);
            }
        }

        @Override
        public void onPackageAdded(String packageName, int uid) {
            synchronized (mLock) {
                if (packageName.equals(mPackageName)) {
                    // package updated, make sure to rebind
                unbind();
                    unbindLocked();
                }
                // check the new package is case it is better
            bindBestPackage(packageName);
                bindBestPackageLocked(packageName);
            }
        }

        @Override
        public void onPackageRemoved(String packageName, int uid) {
            synchronized (mLock) {
                if (packageName.equals(mPackageName)) {
                unbind();
                    unbindLocked();
                    // the currently bound package was removed,
                    // need to search for a new package
                bindBestPackage(null);
                    bindBestPackageLocked(null);
                }
            }
        }
    };
@@ -271,4 +278,12 @@ public class ServiceWatcher implements ServiceConnection {
            return mBinder;
        }
    }

    public void switchUser(int userId) {
        synchronized (mLock) {
            unbindLocked();
            mCurrentUserId = userId;
            bindBestPackageLocked(null);
        }
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.location.Address;
import android.location.GeocoderParams;
import android.location.IGeocodeProvider;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;

import com.android.server.ServiceWatcher;
@@ -38,8 +39,8 @@ public class GeocoderProxy {
    private final ServiceWatcher mServiceWatcher;

    public static GeocoderProxy createAndBind(Context context,
            List<String> initialPackageNames) {
        GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames);
            List<String> initialPackageNames, int userId) {
        GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames, userId);
        if (proxy.bind()) {
            return proxy;
        } else {
@@ -47,11 +48,11 @@ public class GeocoderProxy {
        }
    }

    public GeocoderProxy(Context context, List<String> initialPackageNames) {
    public GeocoderProxy(Context context, List<String> initialPackageNames, int userId) {
        mContext = context;

        mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, initialPackageNames,
                null, null);
                null, null, userId);
    }

    private boolean bind () {
+5 −0
Original line number Diff line number Diff line
@@ -783,6 +783,11 @@ public class GpsLocationProvider implements LocationProviderInterface {
        sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
    }

    @Override
    public void switchUser(int userId) {
        // nothing to do here
    }

    private void handleSetRequest(ProviderRequest request, WorkSource source) {
        if (DEBUG) Log.d(TAG, "setRequest " + request);

Loading