From cace44f497ba9f3fae23ad93865914f1e311fdd6 Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Tue, 9 Apr 2024 15:41:26 +0000 Subject: [PATCH] Fix location services for all profiles Bug: 330538899 Bug: 333507072 Test: atest LocationInjectedServicesPreferenceControllerTest Test: manual Change-Id: Ia42e59b73b9b7c84ecb89082e968b801e1fd1302 --- res/xml/location_services_private_profile.xml | 22 +++++ ...ProfileSelectLocationServicesFragment.java | 3 +- ...jectedServiceBasePreferenceController.java | 25 +++++- ...ForPrivateProfilePreferenceController.java | 66 ++++++++++++++ ...edServicesForWorkPreferenceController.java | 4 +- .../LocationServicesForPrivateProfile.java | 48 ++++++++++ ...ectedServicesPreferenceControllerTest.java | 88 ++++++++++++++++++- 7 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 res/xml/location_services_private_profile.xml create mode 100644 src/com/android/settings/location/LocationInjectedServicesForPrivateProfilePreferenceController.java create mode 100644 src/com/android/settings/location/LocationServicesForPrivateProfile.java diff --git a/res/xml/location_services_private_profile.xml b/res/xml/location_services_private_profile.xml new file mode 100644 index 00000000000..d739e15542b --- /dev/null +++ b/res/xml/location_services_private_profile.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java index 8e48c7bcaa5..ddcc27f16be 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java @@ -20,6 +20,7 @@ import androidx.fragment.app.Fragment; import com.android.settings.R; import com.android.settings.location.LocationServices; +import com.android.settings.location.LocationServicesForPrivateProfile; import com.android.settings.location.LocationServicesForWork; /** @@ -34,7 +35,7 @@ public class ProfileSelectLocationServicesFragment extends ProfileSelectFragment null /* bundle */, LocationServices::new, LocationServicesForWork::new, - LocationServices::new); + LocationServicesForPrivateProfile::new); } @Override diff --git a/src/com/android/settings/location/LocationInjectedServiceBasePreferenceController.java b/src/com/android/settings/location/LocationInjectedServiceBasePreferenceController.java index 5ee83ddaef4..bab3316432c 100644 --- a/src/com/android/settings/location/LocationInjectedServiceBasePreferenceController.java +++ b/src/com/android/settings/location/LocationInjectedServiceBasePreferenceController.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.location.SettingInjectorService; import android.os.UserHandle; +import android.util.ArraySet; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -32,6 +33,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settingslib.core.lifecycle.LifecycleObserver; import java.util.List; @@ -110,13 +112,28 @@ public abstract class LocationInjectedServiceBasePreferenceController } protected Map> getLocationServices() { + ArraySet userHandles = new ArraySet<>(); + userHandles.add(UserHandle.of(UserHandle.myUserId())); + // If location access is locked down by device policy then we only show injected settings // for the primary profile. - final int profileUserId = Utils.getManagedProfileId(mUserManager, UserHandle.myUserId()); + final int managedProfileId = Utils.getManagedProfileId(mUserManager, UserHandle.myUserId()); + if (managedProfileId != UserHandle.USER_NULL + && mLocationEnabler.getShareLocationEnforcedAdmin(managedProfileId) == null) { + userHandles.add(UserHandle.of(managedProfileId)); + } + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + final UserHandle privateProfile = Utils.getProfileOfType(mUserManager, + ProfileSelectFragment.ProfileType.PRIVATE); + if (privateProfile != null && mLocationEnabler + .getShareLocationEnforcedAdmin(privateProfile.getIdentifier()) == null) { + userHandles.add(privateProfile); + } + } return mInjector.getInjectedSettings(mFragment.getPreferenceManager().getContext(), - (profileUserId != UserHandle.USER_NULL - && mLocationEnabler.getShareLocationEnforcedAdmin(profileUserId) != null) - ? UserHandle.myUserId() : UserHandle.USER_CURRENT); + userHandles); } } diff --git a/src/com/android/settings/location/LocationInjectedServicesForPrivateProfilePreferenceController.java b/src/com/android/settings/location/LocationInjectedServicesForPrivateProfilePreferenceController.java new file mode 100644 index 00000000000..7266b2043ae --- /dev/null +++ b/src/com/android/settings/location/LocationInjectedServicesForPrivateProfilePreferenceController.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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.settings.location; + +import android.content.Context; +import android.os.UserHandle; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.Utils; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment; +import com.android.settings.widget.RestrictedAppPreference; + +import java.util.List; +import java.util.Map; + +/** + * Retrieve the Location Services used in private profile user. + */ +public class LocationInjectedServicesForPrivateProfilePreferenceController extends + LocationInjectedServiceBasePreferenceController { + public LocationInjectedServicesForPrivateProfilePreferenceController( + Context context, String key) { + super(context, key); + } + + @Override + protected void injectLocationServices(PreferenceScreen screen) { + if (!android.os.Flags.allowPrivateProfile() + || !android.multiuser.Flags.enablePrivateSpaceFeatures() + || !android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + return; + } + final UserHandle privateProfile = Utils.getProfileOfType(mUserManager, + ProfileSelectFragment.ProfileType.PRIVATE); + if (privateProfile == null) { + return; + } + final Map> prefs = getLocationServices(); + for (Map.Entry> entry : prefs.entrySet()) { + for (Preference pref : entry.getValue()) { + if (pref instanceof RestrictedAppPreference) { + ((RestrictedAppPreference) pref).checkRestrictionAndSetDisabled(); + } + } + if (entry.getKey() == privateProfile.getIdentifier()) { + LocationSettings.addPreferencesSorted(entry.getValue(), screen); + } + } + } +} diff --git a/src/com/android/settings/location/LocationInjectedServicesForWorkPreferenceController.java b/src/com/android/settings/location/LocationInjectedServicesForWorkPreferenceController.java index a8a13b3ca76..f9a4def5c6f 100644 --- a/src/com/android/settings/location/LocationInjectedServicesForWorkPreferenceController.java +++ b/src/com/android/settings/location/LocationInjectedServicesForWorkPreferenceController.java @@ -22,6 +22,7 @@ import android.os.UserHandle; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.Utils; import com.android.settings.widget.RestrictedAppPreference; import java.util.List; @@ -40,6 +41,7 @@ public class LocationInjectedServicesForWorkPreferenceController extends @Override protected void injectLocationServices(PreferenceScreen screen) { + final int managedProfileId = Utils.getManagedProfileId(mUserManager, UserHandle.myUserId()); final Map> prefs = getLocationServices(); for (Map.Entry> entry : prefs.entrySet()) { for (Preference pref : entry.getValue()) { @@ -47,7 +49,7 @@ public class LocationInjectedServicesForWorkPreferenceController extends ((RestrictedAppPreference) pref).checkRestrictionAndSetDisabled(); } } - if (entry.getKey() != UserHandle.myUserId()) { + if (entry.getKey() == managedProfileId) { LocationSettings.addPreferencesSorted(entry.getValue(), screen); } } diff --git a/src/com/android/settings/location/LocationServicesForPrivateProfile.java b/src/com/android/settings/location/LocationServicesForPrivateProfile.java new file mode 100644 index 00000000000..0c8ee6f5fa4 --- /dev/null +++ b/src/com/android/settings/location/LocationServicesForPrivateProfile.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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.settings.location; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; + +public class LocationServicesForPrivateProfile extends DashboardFragment { + private static final String TAG = "LocationServicesForPrivateProfile"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.LOCATION_SERVICES_FOR_PRIVATE_PROFILE; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.location_services_private_profile; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(LocationInjectedServicesForPrivateProfilePreferenceController.class).init(this); + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java index 43b9839a801..375e1520096 100644 --- a/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java @@ -31,8 +31,10 @@ import android.content.Context; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.ArrayMap; +import android.util.ArraySet; import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; @@ -50,6 +52,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -65,6 +68,8 @@ import java.util.Map; public class LocationInjectedServicesPreferenceControllerTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String KEY_LOCATION_SERVICES = "location_service"; @@ -140,8 +145,13 @@ public class LocationInjectedServicesPreferenceControllerTest { when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(componentName); mController.displayPreference(mScreen); + + ArgumentCaptor> profilesArgumentCaptor = + ArgumentCaptor.forClass(ArraySet.class); verify(mSettingsInjector).getInjectedSettings( - any(Context.class), eq(UserHandle.myUserId())); + any(Context.class), profilesArgumentCaptor.capture()); + assertThat(profilesArgumentCaptor.getValue()) + .doesNotContain(UserHandle.of(fakeWorkProfileId)); } @Test @@ -149,6 +159,9 @@ public class LocationInjectedServicesPreferenceControllerTest { final int fakeWorkProfileId = 123; ShadowUserManager.getShadow().setProfileIdsWithDisabled( new int[]{UserHandle.myUserId(), fakeWorkProfileId}); + ShadowUserManager.getShadow().addProfile(new UserInfo(UserHandle.myUserId(), "", 0)); + ShadowUserManager.getShadow().addProfile(new UserInfo(fakeWorkProfileId, "", + UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_PROFILE)); // Mock RestrictedLockUtils.checkIfRestrictionEnforced and let it return null. // Empty enforcing users. @@ -159,8 +172,77 @@ public class LocationInjectedServicesPreferenceControllerTest { enforcingUsers); mController.displayPreference(mScreen); + + ArgumentCaptor> profilesArgumentCaptor = + ArgumentCaptor.forClass(ArraySet.class); verify(mSettingsInjector).getInjectedSettings( - any(Context.class), eq(UserHandle.USER_CURRENT)); + any(Context.class), profilesArgumentCaptor.capture()); + assertThat(profilesArgumentCaptor.getValue()).contains(UserHandle.of(fakeWorkProfileId)); + } + + @Test + public void privateProfileDisallowShareLocationOn_getParentUserLocationServicesOnly() { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES, + android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE); + final int fakePrivateProfileId = 123; + ShadowUserManager.getShadow().setProfileIdsWithDisabled( + new int[]{UserHandle.myUserId(), fakePrivateProfileId}); + ShadowUserManager.getShadow().addProfile(new UserInfo(UserHandle.myUserId(), "", 0)); + ShadowUserManager.getShadow().setPrivateProfile(fakePrivateProfileId, "private", 0); + ShadowUserManager.getShadow().addUserProfile(UserHandle.of(fakePrivateProfileId)); + + // Mock RestrictedLockUtils.checkIfRestrictionEnforced and let it return non-null. + final List enforcingUsers = new ArrayList<>(); + enforcingUsers.add(new UserManager.EnforcingUser(fakePrivateProfileId, + UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); + final ComponentName componentName = new ComponentName("test", "test"); + // Ensure that RestrictedLockUtils.checkIfRestrictionEnforced doesn't return null. + ShadowUserManager.getShadow().setUserRestrictionSources( + UserManager.DISALLOW_SHARE_LOCATION, + UserHandle.of(fakePrivateProfileId), + enforcingUsers); + when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(componentName); + + mController.displayPreference(mScreen); + + ArgumentCaptor> profilesArgumentCaptor = + ArgumentCaptor.forClass(ArraySet.class); + verify(mSettingsInjector).getInjectedSettings( + any(Context.class), profilesArgumentCaptor.capture()); + assertThat(profilesArgumentCaptor.getValue()) + .doesNotContain(UserHandle.of(fakePrivateProfileId)); + } + + @Test + public void privateProfileDisallowShareLocationOff_getAllUserLocationServices() { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES, + android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE); + final int fakePrivateProfileId = 123; + ShadowUserManager.getShadow().setProfileIdsWithDisabled( + new int[]{UserHandle.myUserId(), fakePrivateProfileId}); + ShadowUserManager.getShadow().addProfile(new UserInfo(UserHandle.myUserId(), "", 0)); + ShadowUserManager.getShadow().setPrivateProfile(fakePrivateProfileId, "private", 0); + ShadowUserManager.getShadow().addUserProfile(UserHandle.of(fakePrivateProfileId)); + + // Mock RestrictedLockUtils.checkIfRestrictionEnforced and let it return null. + // Empty enforcing users. + final List enforcingUsers = new ArrayList<>(); + ShadowUserManager.getShadow().setUserRestrictionSources( + UserManager.DISALLOW_SHARE_LOCATION, + UserHandle.of(fakePrivateProfileId), + enforcingUsers); + + mController.displayPreference(mScreen); + + ArgumentCaptor> profilesArgumentCaptor = + ArgumentCaptor.forClass(ArraySet.class); + verify(mSettingsInjector).getInjectedSettings( + any(Context.class), profilesArgumentCaptor.capture()); + assertThat(profilesArgumentCaptor.getValue()).contains(UserHandle.of(fakePrivateProfileId)); } @Test @@ -180,7 +262,7 @@ public class LocationInjectedServicesPreferenceControllerTest { final Map> map = new ArrayMap<>(); map.put(UserHandle.myUserId(), preferences); doReturn(map).when(mSettingsInjector) - .getInjectedSettings(any(Context.class), anyInt()); + .getInjectedSettings(any(Context.class), any(ArraySet.class)); ShadowUserManager.getShadow().setProfileIdsWithDisabled(new int[]{UserHandle.myUserId()}); final int userId = UserHandle.myUserId();