Updates location services to support concurrent multi user.

Allows users that run on multi display to control MLS independently by
redefining whether a registration is currently active or not. For an
registration to be considered active, one of the necessary conditions
is the caller being a current user. With this change, the condition is
changed to be the caller being a visible user.

Bug: 236139244
Bug: 241604546

Test: atest FrameworksMockingServicesTests:LocationProviderManagerTest
Test: atest FrameworksMockingServicesTests:SystemUserInfoHelperTest
Test: manual test in emulator

Change-Id: I8cf1d6148a0465fe85953edc92273cc926f7f40a
This commit is contained in:
Grace Cheng
2022-10-31 18:57:11 +00:00
parent f6396c6870
commit 361e410670
10 changed files with 225 additions and 26 deletions

View File

@@ -140,9 +140,7 @@ import com.android.server.location.provider.StationaryThrottlingLocationProvider
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -310,10 +308,6 @@ public class LocationManagerService extends ILocationManager.Stub implements
permissionManagerInternal.setLocationExtraPackagesProvider(
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationExtraPackageNames));
// TODO(b/241604546): properly handle this callback
LocalServices.getService(UserManagerInternal.class).addUserVisibilityListener(
(u, v) -> Slogf.i(TAG, "onUserVisibilityChanged(): %d -> %b", u, v));
}
@Nullable
@@ -1702,7 +1696,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
private final Context mContext;
private final UserInfoHelper mUserInfoHelper;
private final SystemUserInfoHelper mUserInfoHelper;
private final LocationSettings mLocationSettings;
private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
@@ -1725,7 +1719,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
@GuardedBy("this")
private boolean mSystemReady;
SystemInjector(Context context, UserInfoHelper userInfoHelper) {
SystemInjector(Context context, SystemUserInfoHelper userInfoHelper) {
mContext = context;
mUserInfoHelper = userInfoHelper;
@@ -1745,6 +1739,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
synchronized void onSystemReady() {
mUserInfoHelper.onSystemReady();
mAppOpsHelper.onSystemReady();
mLocationPermissionsHelper.onSystemReady();
mSettingsHelper.onSystemReady();

View File

@@ -110,6 +110,11 @@ public class LocationEventLog extends LocalEventLog<Object> {
addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
}
/** Logs a user visibility changed event. */
public void logUserVisibilityChanged(int userId, boolean visible) {
addLog(new UserVisibilityChangedEvent(userId, visible));
}
/** Logs a location enabled/disabled event. */
public void logLocationEnabled(int userId, boolean enabled) {
addLog(new LocationEnabledEvent(userId, enabled));
@@ -475,6 +480,22 @@ public class LocationEventLog extends LocalEventLog<Object> {
}
}
private static final class UserVisibilityChangedEvent {
private final int mUserId;
private final boolean mVisible;
UserVisibilityChangedEvent(int userId, boolean visible) {
mUserId = userId;
mVisible = visible;
}
@Override
public String toString() {
return "[u" + mUserId + "] " + (mVisible ? "visible" : "invisible");
}
}
private static final class LocationEnabledEvent {
private final int mUserId;

View File

@@ -387,7 +387,7 @@ public class GeofenceManager extends
if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
return false;
}
if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
return false;
}
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -534,7 +534,10 @@ public class GeofenceManager extends
}
void onUserChanged(int userId, int change) {
if (change == UserListener.CURRENT_USER_CHANGED) {
// current user changes affect whether system server location requests are allowed to access
// location, and visibility changes affect whether any given user may access location.
if (change == UserListener.CURRENT_USER_CHANGED
|| change == UserListener.USER_VISIBILITY_CHANGED) {
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
}

View File

@@ -317,7 +317,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
identity.getUserId())) {
return false;
}
if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
return false;
}
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -394,7 +394,10 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
private void onUserChanged(int userId, int change) {
if (change == UserListener.CURRENT_USER_CHANGED) {
// current user changes affect whether system server location requests are allowed to access
// location, and visibility changes affect whether any given user may access location.
if (change == UserListener.CURRENT_USER_CHANGED
|| change == UserListener.USER_VISIBILITY_CHANGED) {
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
}

View File

@@ -33,9 +33,11 @@ import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.util.Arrays;
import java.util.Objects;
/**
* Provides accessors and listeners for all user info.
@@ -50,11 +52,21 @@ public class SystemUserInfoHelper extends UserInfoHelper {
@Nullable private IActivityManager mActivityManager;
@GuardedBy("this")
@Nullable private UserManager mUserManager;
@GuardedBy("this")
@Nullable private UserManagerInternal mUserManagerInternal;
public SystemUserInfoHelper(Context context) {
mContext = context;
}
/** The function should be called when PHASE_SYSTEM_SERVICES_READY. */
public synchronized void onSystemReady() {
mUserManagerInternal =
Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
mUserManagerInternal.addUserVisibilityListener(
(userId, visible) -> dispatchOnVisibleUserChanged(userId, visible));
}
@Nullable
protected final ActivityManagerInternal getActivityManagerInternal() {
synchronized (this) {
@@ -135,6 +147,24 @@ public class SystemUserInfoHelper extends UserInfoHelper {
}
}
@Override
public boolean isVisibleUserId(@UserIdInt int userId) {
synchronized (this) {
// if you're hitting this precondition then you are invoking this before the system is
// ready
Preconditions.checkState(mUserManagerInternal != null);
}
final long identity = Binder.clearCallingIdentity();
try {
synchronized (this) {
return mUserManagerInternal.isUserVisible(userId);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
protected int[] getProfileIds(@UserIdInt int userId) {
UserManager userManager = getUserManager();

View File

@@ -22,6 +22,7 @@ import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_VISIBILITY_CHANGED;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
@@ -47,8 +48,9 @@ public abstract class UserInfoHelper {
int CURRENT_USER_CHANGED = 1;
int USER_STARTED = 2;
int USER_STOPPED = 3;
int USER_VISIBILITY_CHANGED = 4;
@IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED})
@IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED, USER_VISIBILITY_CHANGED})
@Retention(RetentionPolicy.SOURCE)
@interface UserChange {}
@@ -121,6 +123,18 @@ public abstract class UserInfoHelper {
}
}
protected final void dispatchOnVisibleUserChanged(@UserIdInt int userId, boolean visible) {
if (D) {
Log.d(TAG, "visibility of u" + userId + " changed to "
+ (visible ? "visible" : "invisible"));
}
EVENT_LOG.logUserVisibilityChanged(userId, visible);
for (UserListener listener : mListeners) {
listener.onUserChanged(userId, USER_VISIBILITY_CHANGED);
}
}
/**
* Returns an array of running user ids. This will include all running users, and will also
* include any profiles of the running users. The caller must never mutate the returned
@@ -129,8 +143,8 @@ public abstract class UserInfoHelper {
public abstract int[] getRunningUserIds();
/**
* Returns true if the given user id is either the current user or a profile of the current
* user.
* Returns {@code true} if the given user id is either the current user or a profile of the
* current user.
*/
public abstract boolean isCurrentUserId(@UserIdInt int userId);
@@ -140,6 +154,13 @@ public abstract class UserInfoHelper {
*/
public abstract @UserIdInt int getCurrentUserId();
/**
* Returns {@code true} if the user is visible.
*
* <p>The visibility of a user is defined by {@link android.os.UserManager#isUserVisible()}.
*/
public abstract boolean isVisibleUserId(@UserIdInt int userId);
protected abstract int[] getProfileIds(@UserIdInt int userId);
/**

View File

@@ -2193,7 +2193,7 @@ public class LocationProviderManager extends
if (!isEnabled(identity.getUserId())) {
return false;
}
if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
if (!mUserHelper.isVisibleUserId(identity.getUserId())) {
return false;
}
}
@@ -2322,6 +2322,10 @@ public class LocationProviderManager extends
switch (change) {
case UserListener.CURRENT_USER_CHANGED:
// current user changes affect whether system server location requests are
// allowed to access location, and visibility changes affect whether any given
// user may access location.
case UserListener.USER_VISIBILITY_CHANGED:
updateRegistrations(
registration -> registration.getIdentity().getUserId() == userId);
break;

View File

@@ -34,14 +34,16 @@ public class FakeUserInfoHelper extends UserInfoHelper {
public static final int DEFAULT_USERID = 0;
private final IntArray mRunningUserIds;
private final IntArray mVisibleUserIds;
private final SparseArray<IntArray> mProfiles;
private int mCurrentUserId;
public FakeUserInfoHelper() {
mCurrentUserId = DEFAULT_USERID;
mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID});
mRunningUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
mProfiles = new SparseArray<>();
mVisibleUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
}
public void startUser(int userId) {
@@ -65,6 +67,7 @@ public class FakeUserInfoHelper extends UserInfoHelper {
mRunningUserIds.remove(idx);
}
setUserInvisibleInternal(userId);
dispatchOnUserStopped(userId);
}
@@ -82,16 +85,39 @@ public class FakeUserInfoHelper extends UserInfoHelper {
// ensure all profiles are started if they didn't exist before...
for (int userId : currentProfileUserIds) {
startUserInternal(userId, false);
setUserVisibleInternal(userId, true);
}
if (oldUserId != mCurrentUserId) {
dispatchOnCurrentUserChanged(oldUserId, mCurrentUserId);
setUserVisibleInternal(mCurrentUserId, true);
}
}
@Override
public int[] getRunningUserIds() {
return mRunningUserIds.toArray();
private void setUserVisibleInternal(int userId, boolean alwaysDispatch) {
int idx = mVisibleUserIds.indexOf(userId);
if (idx < 0) {
mVisibleUserIds.add(userId);
} else if (!alwaysDispatch) {
return;
}
dispatchOnVisibleUserChanged(userId, true);
}
private void setUserInvisibleInternal(int userId) {
int idx = mVisibleUserIds.indexOf(userId);
if (idx >= 0) {
mVisibleUserIds.remove(userId);
}
dispatchOnVisibleUserChanged(userId, false);
}
public void setUserVisible(int userId, boolean visible) {
if (visible) {
setUserVisibleInternal(userId, true);
} else {
setUserInvisibleInternal(userId);
}
}
@Override
@@ -99,11 +125,21 @@ public class FakeUserInfoHelper extends UserInfoHelper {
return ArrayUtils.contains(getProfileIds(mCurrentUserId), userId);
}
@Override
public int[] getRunningUserIds() {
return mRunningUserIds.toArray();
}
@Override
public int getCurrentUserId() {
return mCurrentUserId;
}
@Override
public boolean isVisibleUserId(int userId) {
return mVisibleUserIds.indexOf(userId) >= 0;
}
@Override
protected int[] getProfileIds(int userId) {
IntArray profiles = mProfiles.get(userId);

View File

@@ -15,6 +15,7 @@
*/
package com.android.server.location.injector;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -31,6 +32,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.location.injector.UserInfoHelper.UserListener;
import com.android.server.pm.UserManagerInternal;
import org.junit.After;
import org.junit.Before;
@@ -45,13 +47,14 @@ public class SystemUserInfoHelperTest {
private static final int USER1_ID = 1;
private static final int USER1_MANAGED_ID = 11;
private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
private static final int[] USER1_PROFILES = new int[] {USER1_ID, USER1_MANAGED_ID};
private static final int USER2_ID = 2;
private static final int USER2_MANAGED_ID = 12;
private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
private static final int[] USER2_PROFILES = new int[] {USER2_ID, USER2_MANAGED_ID};
@Mock private Context mContext;
@Mock private UserManager mUserManager;
@Mock private UserManagerInternal mUserManagerInternal;
private SystemUserInfoHelper mHelper;
@@ -63,12 +66,15 @@ public class SystemUserInfoHelperTest {
doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID);
doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID);
LocalServices.addService(UserManagerInternal.class, mUserManagerInternal);
mHelper = new SystemUserInfoHelper(mContext);
}
@After
public void tearDown() {
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
}
@Test
@@ -77,11 +83,11 @@ public class SystemUserInfoHelperTest {
mHelper.addListener(listener);
mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID);
verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID,
verify(listener).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
verify(listener).onUserChanged(USER1_MANAGED_ID,
UserListener.CURRENT_USER_CHANGED);
verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID,
verify(listener).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
verify(listener).onUserChanged(USER2_MANAGED_ID,
UserListener.CURRENT_USER_CHANGED);
mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID);
@@ -93,6 +99,25 @@ public class SystemUserInfoHelperTest {
UserListener.CURRENT_USER_CHANGED);
}
@Test
public void testListener_UserVisibilityChanged() {
mHelper.onSystemReady();
verify(mUserManagerInternal).addUserVisibilityListener(any());
UserListener listener = mock(UserListener.class);
mHelper.addListener(listener);
mHelper.dispatchOnVisibleUserChanged(USER1_ID, false);
mHelper.dispatchOnVisibleUserChanged(USER2_ID, true);
verify(listener).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
verify(listener).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
mHelper.dispatchOnVisibleUserChanged(USER2_ID, false);
mHelper.dispatchOnVisibleUserChanged(USER1_ID, true);
verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
}
@Test
public void testListener_StartUser() {
UserListener listener = mock(UserListener.class);

View File

@@ -175,7 +175,9 @@ public class LocationProviderManagerTest {
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
mInjector = new TestInjector(mContext);
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
mInjector.getUserInfoHelper().startUser(OTHER_USER);
mInjector.getUserInfoHelper().setUserVisible(OTHER_USER, true);
mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager(null);
@@ -330,6 +332,20 @@ public class LocationProviderManagerTest {
assertThat(coarse).isNearby(loc, 5000);
}
@Test
public void testGetLastLocation_InvisibleUser() {
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
PERMISSION_FINE)).isNull();
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
PERMISSION_FINE)).isEqualTo(loc);
}
@Test
public void testGetLastLocation_Bypass() {
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
@@ -568,6 +584,25 @@ public class LocationProviderManagerTest {
nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_InvisibleUser() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = new LocationRequest.Builder(0)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
mProvider.setProviderLocation(createLocationResult(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(List.class),
nullable(IRemoteCallback.class));
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
LocationResult loc = createLocationResult(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
}
@Test
public void testRegisterListener_ExpiringAlarm() throws Exception {
ILocationListener listener = createMockLocationListener();
@@ -798,6 +833,17 @@ public class LocationProviderManagerTest {
verify(listener, times(1)).onLocation(isNull());
}
@Test
public void testGetCurrentLocation_InvisibleUser() throws Exception {
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
ILocationCallback listener = createMockGetCurrentLocationListener();
LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
verify(listener).onLocation(isNull());
}
@Test
public void testFlush() throws Exception {
ILocationListener listener = createMockLocationListener();
@@ -1007,6 +1053,21 @@ public class LocationProviderManagerTest {
mInjector.getSettingsHelper().getBackgroundThrottleIntervalMs());
}
@Test
public void testProviderRequest_InvisibleUser() {
ILocationListener listener = createMockLocationListener();
LocationRequest request = new LocationRequest.Builder(5)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
assertThat(mProvider.getRequest().isActive()).isFalse();
mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
assertThat(mProvider.getRequest().isActive()).isTrue();
}
@Test
public void testProviderRequest_IgnoreLocationSettings() {
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(