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

Commit 50d61baf authored by Payam Pakzad's avatar Payam Pakzad Committed by Android (Google) Code Review
Browse files

Merge "RESTRICT AUTOMERGE: Update QS rotation tile text to display when...

Merge "RESTRICT AUTOMERGE: Update QS rotation tile text to display when smart-auto-rotate is enabled" into sc-qpr1-dev
parents 69617682 dae76358
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1779,6 +1779,8 @@
    <string name="tuner_full_importance_settings">Power notification controls</string>
    <string name="tuner_full_importance_settings_on">On</string>
    <string name="tuner_full_importance_settings_off">Off</string>
    <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
    <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
    <string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
        \n\n<b>Level 5</b>
        \n- Show at the top of the notification list
+68 −2
Original line number Diff line number Diff line
@@ -16,12 +16,19 @@

package com.android.systemui.qs.tiles;

import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.view.View;
import android.widget.Switch;
@@ -38,18 +45,25 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
import com.android.systemui.util.settings.SecureSettings;

import javax.inject.Inject;

/** Quick settings tile: Rotation **/
public class RotationLockTile extends QSTileImpl<BooleanState> {
public class RotationLockTile extends QSTileImpl<BooleanState> implements
        BatteryController.BatteryStateChangeCallback {

    private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
    private final RotationLockController mController;
    private final SensorPrivacyManager mPrivacyManager;
    private final BatteryController mBatteryController;
    private final SecureSetting mSetting;

    @Inject
    public RotationLockTile(
@@ -61,12 +75,38 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
            StatusBarStateController statusBarStateController,
            ActivityStarter activityStarter,
            QSLogger qsLogger,
            RotationLockController rotationLockController
            RotationLockController rotationLockController,
            SensorPrivacyManager privacyManager,
            BatteryController batteryController,
            SecureSettings secureSettings
    ) {
        super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                statusBarStateController, activityStarter, qsLogger);
        mController = rotationLockController;
        mController.observe(this, mCallback);
        mPrivacyManager = privacyManager;
        mBatteryController = batteryController;
        mPrivacyManager
                .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> refreshState());
        int currentUser = host.getUserContext().getUserId();
        mSetting = new SecureSetting(
                secureSettings,
                mHandler,
                Secure.CAMERA_AUTOROTATE,
                currentUser
        ) {
            @Override
            protected void handleValueChanged(int value, boolean observedChange) {
                // mHandler is the background handler so calling this is OK
                handleRefreshState(value);
            }
        };
        mBatteryController.observe(getLifecycle(), this);
    }

    @Override
    public void onPowerSaveChanged(boolean isPowerSave) {
        refreshState();
    }

    @Override
@@ -95,14 +135,33 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
    protected void handleUpdateState(BooleanState state, Object arg) {
        final boolean rotationLocked = mController.isRotationLocked();

        final boolean powerSave = mBatteryController.isPowerSave();
        final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(
                SensorPrivacyManager.Sensors.CAMERA);
        final boolean cameraRotation =
                !powerSave && !cameraLocked && hasSufficientPermission(mContext)
                        && mController.isCameraRotationEnabled();
        state.value = !rotationLocked;
        state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
        state.icon = mIcon;
        state.contentDescription = getAccessibilityString(rotationLocked);
        if (!rotationLocked && cameraRotation) {
            state.secondaryLabel = mContext.getResources().getString(
                    R.string.rotation_lock_camera_rotation_on);
        } else {
            state.secondaryLabel = "";
        }

        state.expandedAccessibilityClassName = Switch.class.getName();
        state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
    }

    @Override
    protected void handleUserSwitch(int newUserId) {
        mSetting.setUserId(newUserId);
        handleRefreshState(mSetting.getValue());
    }

    public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
            Resources resources) {
        int lockOrientation = controller.getRotationLockOrientation();
@@ -140,4 +199,11 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
            refreshState(rotationLocked);
        }
    };

    private boolean hasSufficientPermission(Context context) {
        final PackageManager packageManager = context.getPackageManager();
        final String rotationPackage = packageManager.getRotationResolverPackageName();
        return rotationPackage != null && packageManager.checkPermission(
                Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ public interface RotationLockController extends Listenable,
    int getRotationLockOrientation();
    boolean isRotationLockAffordanceVisible();
    boolean isRotationLocked();
    boolean isCameraRotationEnabled();
    void setRotationLocked(boolean locked);
    void setRotationLockedAtAngle(boolean locked, int rotation);

+16 −7
Original line number Diff line number Diff line
@@ -18,11 +18,13 @@ package com.android.systemui.statusbar.policy;

import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings.Secure;

import androidx.annotation.NonNull;

import com.android.internal.view.RotationPolicy;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.settings.SecureSettings;

import java.util.concurrent.CopyOnWriteArrayList;

@@ -32,6 +34,7 @@ import javax.inject.Inject;
@SysUISingleton
public final class RotationLockControllerImpl implements RotationLockController {
    private final Context mContext;
    private final SecureSettings mSecureSettings;
    private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
            new CopyOnWriteArrayList<RotationLockControllerCallback>();

@@ -44,8 +47,9 @@ public final class RotationLockControllerImpl implements RotationLockController
            };

    @Inject
    public RotationLockControllerImpl(Context context) {
    public RotationLockControllerImpl(Context context, SecureSettings secureSettings) {
        mContext = context;
        mSecureSettings = secureSettings;
        setListening(true);
    }

@@ -68,6 +72,11 @@ public final class RotationLockControllerImpl implements RotationLockController
        return RotationPolicy.isRotationLocked(mContext);
    }

    public boolean isCameraRotationEnabled() {
        return mSecureSettings.getIntForUser(Secure.CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT)
                == 1;
    }

    public void setRotationLocked(boolean locked) {
        RotationPolicy.setRotationLock(mContext, locked);
    }
+209 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.systemui.qs.tiles;

import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;

import static junit.framework.TestCase.assertEquals;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import android.Manifest;
import android.content.pm.PackageManager;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;

import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;


@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class RotationLockTileTest extends SysuiTestCase {

    private static final String PACKAGE_NAME = "package_name";

    @Mock
    private PackageManager mPackageManager;
    @Mock
    private ActivityStarter mActivityStarter;
    @Mock
    private QSTileHost mHost;
    @Mock
    private MetricsLogger mMetricsLogger;
    @Mock
    private StatusBarStateController mStatusBarStateController;
    @Mock
    private QSLogger mQSLogger;
    @Mock
    private SensorPrivacyManager mPrivacyManager;
    @Mock
    private BatteryController mBatteryController;

    private SecureSettings mSecureSettings;
    private RotationLockController mController;
    private TestableLooper mTestableLooper;
    private RotationLockTile mLockTile;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mTestableLooper = TestableLooper.get(this);

        when(mHost.getContext()).thenReturn(mContext);
        when(mHost.getUserContext()).thenReturn(mContext);

        mSecureSettings = new FakeSettings();
        mController = new RotationLockControllerImpl(mContext, mSecureSettings);

        mLockTile = new RotationLockTile(
                mHost,
                mTestableLooper.getLooper(),
                new Handler(mTestableLooper.getLooper()),
                new FalsingManagerFake(),
                mMetricsLogger,
                mStatusBarStateController,
                mActivityStarter,
                mQSLogger,
                mController,
                mPrivacyManager,
                mBatteryController,
                mSecureSettings
        );

        mLockTile.initialize();

        // We are not setting the mocks to listening, so we trigger a first refresh state to
        // set the initial state
        mLockTile.refreshState();

        mTestableLooper.processAllMessages();

        mContext.setMockPackageManager(mPackageManager);
        doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
                Manifest.permission.CAMERA, PACKAGE_NAME);

        when(mBatteryController.isPowerSave()).thenReturn(false);
        when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false);
        enableAutoRotation();
        enableCameraBasedRotation();

        mLockTile.refreshState();
        mTestableLooper.processAllMessages();
    }

    @Test
    public void testSecondaryString_cameraRotateOn_returnsFaceBased() {
        assertEquals("On - Face-based", mLockTile.getState().secondaryLabel);
    }

    @Test
    public void testSecondaryString_rotateOff_isEmpty() {
        disableAutoRotation();

        mLockTile.refreshState();
        mTestableLooper.processAllMessages();

        assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
    }

    @Test
    public void testSecondaryString_cameraRotateOff_isEmpty() {
        disableCameraBasedRotation();

        mLockTile.refreshState();
        mTestableLooper.processAllMessages();

        assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
    }

    @Test
    public void testSecondaryString_powerSaveEnabled_isEmpty() {
        when(mBatteryController.isPowerSave()).thenReturn(true);

        mLockTile.refreshState();
        mTestableLooper.processAllMessages();

        assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
    }

    @Test
    public void testSecondaryString_cameraDisabled_isEmpty() {
        when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true);

        mLockTile.refreshState();
        mTestableLooper.processAllMessages();

        assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
    }

    @Test
    public void testSecondaryString_noCameraPermission_isEmpty() {
        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
                Manifest.permission.CAMERA, PACKAGE_NAME);

        mLockTile.refreshState();
        mTestableLooper.processAllMessages();

        assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
    }

    private void enableAutoRotation() {
        Settings.System.putIntForUser(mContext.getContentResolver(),
                Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
    }

    private void disableAutoRotation() {
        Settings.System.putIntForUser(mContext.getContentResolver(),
                Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
    }

    private void enableCameraBasedRotation() {
        mSecureSettings.putIntForUser(
                CAMERA_AUTOROTATE, 1, UserHandle.USER_CURRENT);
    }

    private void disableCameraBasedRotation() {
        mSecureSettings.putIntForUser(
                CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT);
    }
}
Loading