1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.qs.tiles;
18 
19 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
20 
21 import static com.android.systemui.statusbar.policy.RotationLockControllerImpl.hasSufficientPermission;
22 
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.hardware.SensorPrivacyManager;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.provider.Settings;
30 import android.provider.Settings.Secure;
31 import android.service.quicksettings.Tile;
32 import android.widget.Switch;
33 
34 import androidx.annotation.Nullable;
35 
36 import com.android.internal.logging.MetricsLogger;
37 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
38 import com.android.systemui.animation.Expandable;
39 import com.android.systemui.dagger.qualifiers.Background;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.plugins.ActivityStarter;
42 import com.android.systemui.plugins.FalsingManager;
43 import com.android.systemui.plugins.qs.QSTile.BooleanState;
44 import com.android.systemui.plugins.statusbar.StatusBarStateController;
45 import com.android.systemui.qs.QSHost;
46 import com.android.systemui.qs.QsEventLogger;
47 import com.android.systemui.qs.UserSettingObserver;
48 import com.android.systemui.qs.logging.QSLogger;
49 import com.android.systemui.qs.tileimpl.QSTileImpl;
50 import com.android.systemui.res.R;
51 import com.android.systemui.statusbar.policy.BatteryController;
52 import com.android.systemui.statusbar.policy.RotationLockController;
53 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
54 import com.android.systemui.util.settings.SecureSettings;
55 
56 import javax.inject.Inject;
57 
58 /** Quick settings tile: Rotation **/
59 public class RotationLockTile extends QSTileImpl<BooleanState> implements
60         BatteryController.BatteryStateChangeCallback {
61 
62     public static final String TILE_SPEC = "rotation";
63 
64     private static final String EMPTY_SECONDARY_STRING = "";
65 
66     private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
67     private final RotationLockController mController;
68     private final SensorPrivacyManager mPrivacyManager;
69     private final BatteryController mBatteryController;
70     private final UserSettingObserver mSetting;
71     private final boolean mAllowRotationResolver;
72 
73     @Inject
RotationLockTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, RotationLockController rotationLockController, SensorPrivacyManager privacyManager, BatteryController batteryController, SecureSettings secureSettings )74     public RotationLockTile(
75             QSHost host,
76             QsEventLogger uiEventLogger,
77             @Background Looper backgroundLooper,
78             @Main Handler mainHandler,
79             FalsingManager falsingManager,
80             MetricsLogger metricsLogger,
81             StatusBarStateController statusBarStateController,
82             ActivityStarter activityStarter,
83             QSLogger qsLogger,
84             RotationLockController rotationLockController,
85             SensorPrivacyManager privacyManager,
86             BatteryController batteryController,
87             SecureSettings secureSettings
88     ) {
89         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
90                 statusBarStateController, activityStarter, qsLogger);
91         mController = rotationLockController;
92         mController.observe(this, mCallback);
93         mPrivacyManager = privacyManager;
94         mBatteryController = batteryController;
95         int currentUser = host.getUserContext().getUserId();
96         mSetting = new UserSettingObserver(
97                 secureSettings,
98                 mHandler,
99                 Secure.CAMERA_AUTOROTATE,
100                 currentUser
101         ) {
102             @Override
103             protected void handleValueChanged(int value, boolean observedChange) {
104                 // mHandler is the background handler so calling this is OK
105                 handleRefreshState(null);
106             }
107         };
108         mBatteryController.observe(getLifecycle(), this);
109         mAllowRotationResolver = mContext.getResources().getBoolean(
110                 com.android.internal.R.bool.config_allowRotationResolver);
111     }
112 
113     @Override
handleInitialize()114     protected void handleInitialize() {
115         mPrivacyManager.addSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener);
116     }
117 
118     @Override
onPowerSaveChanged(boolean isPowerSave)119     public void onPowerSaveChanged(boolean isPowerSave) {
120         refreshState();
121     }
122 
123     @Override
newTileState()124     public BooleanState newTileState() {
125         return new BooleanState();
126     }
127 
128     @Override
getLongClickIntent()129     public Intent getLongClickIntent() {
130         return new Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS);
131     }
132 
133     @Override
handleClick(@ullable Expandable expandable)134     protected void handleClick(@Nullable Expandable expandable) {
135         final boolean newState = !mState.value;
136         mController.setRotationLocked(!newState, /* caller= */ "RotationLockTile#handleClick");
137         refreshState(newState);
138     }
139 
140     @Override
getTileLabel()141     public CharSequence getTileLabel() {
142         return getState().label;
143     }
144 
145     @Override
handleUpdateState(BooleanState state, Object arg)146     protected void handleUpdateState(BooleanState state, Object arg) {
147         final boolean rotationLocked = mController.isRotationLocked();
148 
149         final boolean powerSave = mBatteryController.isPowerSave();
150         final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA);
151         final boolean cameraRotation = mAllowRotationResolver &&
152                 !powerSave && !cameraLocked && hasSufficientPermission(mContext)
153                         && mController.isCameraRotationEnabled();
154         state.value = !rotationLocked;
155         state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
156         state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off);
157         state.contentDescription = getAccessibilityString(rotationLocked);
158         if (!rotationLocked) {
159             state.secondaryLabel = cameraRotation ? mContext.getResources().getString(
160                     R.string.rotation_lock_camera_rotation_on)
161                     : EMPTY_SECONDARY_STRING;
162             state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on);
163         } else {
164             state.secondaryLabel = EMPTY_SECONDARY_STRING;
165         }
166         state.stateDescription = state.secondaryLabel;
167 
168         state.expandedAccessibilityClassName = Switch.class.getName();
169         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
170     }
171 
172     @Override
handleDestroy()173     protected void handleDestroy() {
174         super.handleDestroy();
175         mSetting.setListening(false);
176         mPrivacyManager.removeSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener);
177     }
178 
179     @Override
handleSetListening(boolean listening)180     public void handleSetListening(boolean listening) {
181         super.handleSetListening(listening);
182         mSetting.setListening(listening);
183     }
184 
185     @Override
handleUserSwitch(int newUserId)186     protected void handleUserSwitch(int newUserId) {
187         mSetting.setUserId(newUserId);
188         handleRefreshState(null);
189     }
190 
isCurrentOrientationLockPortrait(RotationLockController controller, Resources resources)191     public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
192             Resources resources) {
193         int lockOrientation = controller.getRotationLockOrientation();
194         if (lockOrientation == Configuration.ORIENTATION_UNDEFINED) {
195             // Freely rotating device; use current rotation
196             return resources.getConfiguration().orientation
197                     != Configuration.ORIENTATION_LANDSCAPE;
198         } else {
199             return lockOrientation != Configuration.ORIENTATION_LANDSCAPE;
200         }
201     }
202 
203     @Override
getMetricsCategory()204     public int getMetricsCategory() {
205         return MetricsEvent.QS_ROTATIONLOCK;
206     }
207 
208     /**
209      * Get the correct accessibility string based on the state
210      *
211      * @param locked Whether or not rotation is locked.
212      */
getAccessibilityString(boolean locked)213     private String getAccessibilityString(boolean locked) {
214         return mContext.getString(R.string.accessibility_quick_settings_rotation);
215     }
216 
217     private final RotationLockControllerCallback mCallback = new RotationLockControllerCallback() {
218         @Override
219         public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
220             refreshState(rotationLocked);
221         }
222     };
223 
224     private final SensorPrivacyManager.OnSensorPrivacyChangedListener
225             mSensorPrivacyChangedListener =
226             (sensor, enabled) -> refreshState();
227 }
228