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();