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