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

Commit ea341bdf authored by Manish Singh's avatar Manish Singh Committed by Android (Google) Code Review
Browse files

Merge "Add controller for location for private profile" into main

parents 65e6431e dd5a8e61
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -3562,6 +3562,8 @@
    <!-- [CHAR LIMIT=30] Title for managed profile location switch  -->
    <!-- [CHAR LIMIT=30] Title for managed profile location switch  -->
    <string name="managed_profile_location_switch_title">Location for work profile</string>
    <string name="managed_profile_location_switch_title">Location for work profile</string>
    <!-- [CHAR LIMIT=60] Title for private profile location switch  -->
    <string name="private_profile_location_switch_title">Location for private space</string>
    <!-- [CHAR LIMIT=30] Location settings screen. It's a link that directs the user to a page that
    <!-- [CHAR LIMIT=30] Location settings screen. It's a link that directs the user to a page that
      shows the location permission setting for each installed app -->
      shows the location permission setting for each installed app -->
    <string name="location_app_level_permissions">App location permissions</string>
    <string name="location_app_level_permissions">App location permissions</string>
+8 −0
Original line number Original line Diff line number Diff line
@@ -49,6 +49,14 @@
            settings:forWork="true"
            settings:forWork="true"
            settings:useAdminDisabledSummary="true"/>
            settings:useAdminDisabledSummary="true"/>


        <!-- This preference gets removed if there is no private profile -->
        <com.android.settingslib.RestrictedSwitchPreference
            android:enabled="false"
            android:key="private_profile_location_switch"
            android:selectable="true"
            android:title="@string/private_profile_location_switch_title"
            settings:controller="com.android.settings.location.LocationForPrivateProfilePreferenceController"/>

        <!-- This preference category gets removed if new_recent_location_ui is disabled -->
        <!-- This preference category gets removed if new_recent_location_ui is disabled -->
        <Preference
        <Preference
            android:key="app_level_permissions"
            android:key="app_level_permissions"
+117 −0
Original line number Original line Diff line number Diff line
/*
 * 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 android.os.UserManager;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedSwitchPreference;

public class LocationForPrivateProfilePreferenceController
        extends LocationBasePreferenceController {
    @Nullable private RestrictedSwitchPreference mPreference;
    @Nullable private final UserHandle mPrivateProfileHandle;
    public LocationForPrivateProfilePreferenceController(
            @NonNull Context context, @NonNull String key) {
        super(context, key);
        mPrivateProfileHandle = Utils.getProfileOfType(mUserManager, ProfileType.PRIVATE);
    }

    @Override
    public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
        if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
            final boolean switchState = mPreference.isChecked();
            mUserManager.setUserRestriction(
                    UserManager.DISALLOW_SHARE_LOCATION,
                    !switchState,
                    mPrivateProfileHandle);
            mPreference.setSummary(switchState
                    ? R.string.switch_on_text : R.string.switch_off_text);
            return true;
        }
        return false;
    }

    @Override
    public void displayPreference(@NonNull PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
        if (mPreference != null) {
            mPreference.setEnabled(isPrivateProfileAvailable());
        }
    }

    @Override
    public int getAvailabilityStatus() {
        if (!android.os.Flags.allowPrivateProfile()
                || !android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
                || !isPrivateProfileAvailable()) {
            return CONDITIONALLY_UNAVAILABLE;
        }
        return AVAILABLE;
    }

    @Override
    public void onLocationModeChanged(int mode, boolean restricted) {
        if ((mPreference != null && !mPreference.isVisible())
                || !isAvailable()
                || !isPrivateProfileAvailable()) {
            return;
        }

        // The profile owner (which is the admin for the child profile) might have added a location
        // sharing restriction.
        final RestrictedLockUtils.EnforcedAdmin admin =
                mLocationEnabler.getShareLocationEnforcedAdmin(
                        mPrivateProfileHandle.getIdentifier());
        if (admin != null) {
            mPreference.setDisabledByAdmin(admin);
        } else {
            final boolean enabled = mLocationEnabler.isEnabled(mode);
            mPreference.setEnabled(enabled);
            int summaryResId;

            final boolean isRestrictedByBase =
                    mLocationEnabler
                            .hasShareLocationRestriction(mPrivateProfileHandle.getIdentifier());
            if (isRestrictedByBase || !enabled) {
                mPreference.setChecked(false);
                summaryResId = enabled ? R.string.switch_off_text
                        : R.string.location_app_permission_summary_location_off;
            } else {
                mPreference.setChecked(true);
                summaryResId = R.string.switch_on_text;
            }
            mPreference.setSummary(summaryResId);
        }
    }

    private boolean isPrivateProfileAvailable() {
        return mPrivateProfileHandle != null
                && !mUserManager.isQuietModeEnabled(mPrivateProfileHandle);
    }
}
+1 −0
Original line number Original line Diff line number Diff line
@@ -119,6 +119,7 @@ public class LocationSettings extends DashboardFragment implements
        use(RecentLocationAccessSeeAllButtonPreferenceController.class).init(this);
        use(RecentLocationAccessSeeAllButtonPreferenceController.class).init(this);
        use(LocationForWorkPreferenceController.class).init(this);
        use(LocationForWorkPreferenceController.class).init(this);
        use(LocationSettingsFooterPreferenceController.class).init(this);
        use(LocationSettingsFooterPreferenceController.class).init(this);
        use(LocationForPrivateProfilePreferenceController.class).init(this);
    }
    }


    @Override
    @Override
+189 −0
Original line number Original line Diff line number Diff line
/*
 * 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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;

import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.android.settings.R;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.util.ReflectionHelpers;

import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
public class LocationForPrivateProfilePreferenceControllerTest {

    @Mock
    private RestrictedSwitchPreference mPreference;
    @Mock
    private PreferenceScreen mScreen;
    @Mock
    private UserManager mUserManager;
    @Mock
    private LocationEnabler mEnabler;
    @Mock
    private UserHandle mUserHandle;

    private Context mContext;
    private LocationForPrivateProfilePreferenceController mController;
    private LifecycleOwner mLifecycleOwner;
    private Lifecycle mLifecycle;
    private LocationSettings mLocationSettings;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = spy(ApplicationProvider.getApplicationContext());
        doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
        mockPrivateProfile();
        mLifecycleOwner = () -> mLifecycle;
        mLifecycle = new Lifecycle(mLifecycleOwner);
        mLocationSettings = spy(new LocationSettings());
        when(mLocationSettings.getSettingsLifecycle()).thenReturn(mLifecycle);
        mController = new LocationForPrivateProfilePreferenceController(mContext, "key");
        mController.init(mLocationSettings);
        ReflectionHelpers.setField(mController, "mLocationEnabler", mEnabler);
        when(mScreen.findPreference(any())).thenReturn(mPreference);
        final String key = mController.getPreferenceKey();
        when(mPreference.getKey()).thenReturn(key);
        when(mPreference.isVisible()).thenReturn(true);
    }

    @Test
    public void handlePreferenceTreeClick_preferenceChecked_shouldSetRestrictionAndOnSummary() {
        mController.displayPreference(mScreen);
        when(mPreference.isChecked()).thenReturn(true);

        mController.handlePreferenceTreeClick(mPreference);

        verify(mUserManager)
                .setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, false, mUserHandle);
        verify(mPreference).setSummary(R.string.switch_on_text);
    }

    @Test
    public void handlePreferenceTreeClick_preferenceUnchecked_shouldSetRestritionAndOffSummary() {
        mController.displayPreference(mScreen);
        when(mPreference.isChecked()).thenReturn(false);

        mController.handlePreferenceTreeClick(mPreference);

        verify(mUserManager)
                .setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, mUserHandle);
        verify(mPreference).setSummary(R.string.switch_off_text);
    }

    @Test
    public void onLocationModeChanged_disabledByAdmin_shouldDisablePreference() {
        mController.displayPreference(mScreen);
        final EnforcedAdmin admin = mock(EnforcedAdmin.class);
        doReturn(admin).when(mEnabler).getShareLocationEnforcedAdmin(anyInt());
        doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt());

        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false);

        verify(mPreference).setDisabledByAdmin(any());
    }

    @Test
    public void onLocationModeChanged_locationOff_shouldDisablePreference() {
        mController.displayPreference(mScreen);
        doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt());
        doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt());

        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false);

        verify(mPreference).setEnabled(false);
        verify(mPreference).setChecked(false);
        verify(mPreference).setSummary(R.string.location_app_permission_summary_location_off);
    }

    @Test
    public void onLocationModeChanged_locationOn_shouldEnablePreference() {
        mController.displayPreference(mScreen);
        doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt());
        doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt());
        doReturn(true).when(mEnabler).isEnabled(anyInt());

        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false);

        verify(mPreference, times(2)).setEnabled(true);
        verify(mPreference).setSummary(R.string.switch_on_text);
    }

    @Test
    public void onLocationModeChanged_noRestriction_shouldCheckedPreference() {
        mController.displayPreference(mScreen);
        doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt());
        doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt());
        doReturn(true).when(mEnabler).isEnabled(anyInt());

        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false);

        verify(mPreference).setChecked(true);
    }

    @Test
    public void onLocationModeChanged_hasRestriction_shouldCheckedPreference() {
        mController.displayPreference(mScreen);
        doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt());
        doReturn(true).when(mEnabler).hasShareLocationRestriction(anyInt());

        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false);

        verify(mPreference).setChecked(false);
    }

    private void mockPrivateProfile() {
        final List<UserHandle> userProfiles = new ArrayList<>();
        doReturn(9).when(mUserHandle).getIdentifier();
        userProfiles.add(mUserHandle);
        doReturn(userProfiles).when(mUserManager).getUserProfiles();
        doReturn(new UserInfo(
                9,
                "user 9",
                "",
                0,
                UserManager.USER_TYPE_PROFILE_PRIVATE)).when(mUserManager).getUserInfo(9);
    }
}