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 = 67 maybeLoadResourceIcon(com.android.internal.R.drawable.ic_qs_auto_rotate); 68 private final RotationLockController mController; 69 private final SensorPrivacyManager mPrivacyManager; 70 private final BatteryController mBatteryController; 71 private final UserSettingObserver mSetting; 72 private final boolean mAllowRotationResolver; 73 74 @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 )75 public RotationLockTile( 76 QSHost host, 77 QsEventLogger uiEventLogger, 78 @Background Looper backgroundLooper, 79 @Main Handler mainHandler, 80 FalsingManager falsingManager, 81 MetricsLogger metricsLogger, 82 StatusBarStateController statusBarStateController, 83 ActivityStarter activityStarter, 84 QSLogger qsLogger, 85 RotationLockController rotationLockController, 86 SensorPrivacyManager privacyManager, 87 BatteryController batteryController, 88 SecureSettings secureSettings 89 ) { 90 super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, 91 statusBarStateController, activityStarter, qsLogger); 92 mController = rotationLockController; 93 mController.observe(this, mCallback); 94 mPrivacyManager = privacyManager; 95 mBatteryController = batteryController; 96 int currentUser = host.getUserContext().getUserId(); 97 mSetting = new UserSettingObserver( 98 secureSettings, 99 mHandler, 100 Secure.CAMERA_AUTOROTATE, 101 currentUser 102 ) { 103 @Override 104 protected void handleValueChanged(int value, boolean observedChange) { 105 // mHandler is the background handler so calling this is OK 106 handleRefreshState(null); 107 } 108 }; 109 mBatteryController.observe(getLifecycle(), this); 110 mAllowRotationResolver = mContext.getResources().getBoolean( 111 com.android.internal.R.bool.config_allowRotationResolver); 112 } 113 114 @Override handleInitialize()115 protected void handleInitialize() { 116 mPrivacyManager.addSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener); 117 } 118 119 @Override onPowerSaveChanged(boolean isPowerSave)120 public void onPowerSaveChanged(boolean isPowerSave) { 121 refreshState(); 122 } 123 124 @Override newTileState()125 public BooleanState newTileState() { 126 return new BooleanState(); 127 } 128 129 @Override getLongClickIntent()130 public Intent getLongClickIntent() { 131 return new Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS); 132 } 133 134 @Override handleClick(@ullable Expandable expandable)135 protected void handleClick(@Nullable Expandable expandable) { 136 final boolean newState = !mState.value; 137 mController.setRotationLocked(!newState, /* caller= */ "RotationLockTile#handleClick"); 138 refreshState(newState); 139 } 140 141 @Override getTileLabel()142 public CharSequence getTileLabel() { 143 return getState().label; 144 } 145 146 @Override handleUpdateState(BooleanState state, Object arg)147 protected void handleUpdateState(BooleanState state, Object arg) { 148 final boolean rotationLocked = mController.isRotationLocked(); 149 150 final boolean powerSave = mBatteryController.isPowerSave(); 151 final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA); 152 final boolean cameraRotation = mAllowRotationResolver && 153 !powerSave && !cameraLocked && hasSufficientPermission(mContext) 154 && mController.isCameraRotationEnabled(); 155 state.value = !rotationLocked; 156 state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label); 157 state.icon = maybeLoadResourceIcon(R.drawable.qs_auto_rotate_icon_off); 158 state.contentDescription = getAccessibilityString(rotationLocked); 159 if (!rotationLocked) { 160 state.secondaryLabel = cameraRotation ? mContext.getResources().getString( 161 R.string.rotation_lock_camera_rotation_on) 162 : EMPTY_SECONDARY_STRING; 163 state.icon = maybeLoadResourceIcon(R.drawable.qs_auto_rotate_icon_on); 164 } else { 165 state.secondaryLabel = EMPTY_SECONDARY_STRING; 166 } 167 state.stateDescription = state.secondaryLabel; 168 169 state.expandedAccessibilityClassName = Switch.class.getName(); 170 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; 171 } 172 173 @Override handleDestroy()174 protected void handleDestroy() { 175 super.handleDestroy(); 176 mSetting.setListening(false); 177 mPrivacyManager.removeSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener); 178 } 179 180 @Override handleSetListening(boolean listening)181 public void handleSetListening(boolean listening) { 182 super.handleSetListening(listening); 183 mSetting.setListening(listening); 184 } 185 186 @Override handleUserSwitch(int newUserId)187 protected void handleUserSwitch(int newUserId) { 188 mSetting.setUserId(newUserId); 189 handleRefreshState(null); 190 } 191 isCurrentOrientationLockPortrait(RotationLockController controller, Resources resources)192 public static boolean isCurrentOrientationLockPortrait(RotationLockController controller, 193 Resources resources) { 194 int lockOrientation = controller.getRotationLockOrientation(); 195 if (lockOrientation == Configuration.ORIENTATION_UNDEFINED) { 196 // Freely rotating device; use current rotation 197 return resources.getConfiguration().orientation 198 != Configuration.ORIENTATION_LANDSCAPE; 199 } else { 200 return lockOrientation != Configuration.ORIENTATION_LANDSCAPE; 201 } 202 } 203 204 @Override getMetricsCategory()205 public int getMetricsCategory() { 206 return MetricsEvent.QS_ROTATIONLOCK; 207 } 208 209 /** 210 * Get the correct accessibility string based on the state 211 * 212 * @param locked Whether or not rotation is locked. 213 */ getAccessibilityString(boolean locked)214 private String getAccessibilityString(boolean locked) { 215 return mContext.getString(R.string.accessibility_quick_settings_rotation); 216 } 217 218 private final RotationLockControllerCallback mCallback = new RotationLockControllerCallback() { 219 @Override 220 public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) { 221 refreshState(rotationLocked); 222 } 223 }; 224 225 private final SensorPrivacyManager.OnSensorPrivacyChangedListener 226 mSensorPrivacyChangedListener = 227 (sensor, enabled) -> refreshState(); 228 } 229