1 /* 2 * Copyright (C) 2021 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.statusbar.policy; 18 19 import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; 20 import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; 21 import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.Mockito.verify; 27 28 import android.hardware.devicestate.DeviceState; 29 import android.hardware.devicestate.DeviceStateManager; 30 import android.os.UserHandle; 31 import android.provider.Settings; 32 import android.testing.TestableContentResolver; 33 import android.testing.TestableResources; 34 35 import androidx.test.ext.junit.runners.AndroidJUnit4; 36 import androidx.test.filters.SmallTest; 37 38 import com.android.internal.R; 39 import com.android.internal.view.RotationPolicy; 40 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; 41 import com.android.systemui.SysuiTestCase; 42 import com.android.systemui.dump.DumpManager; 43 import com.android.systemui.util.concurrency.FakeExecutor; 44 import com.android.systemui.util.time.FakeSystemClock; 45 import com.android.systemui.util.wrapper.RotationPolicyWrapper; 46 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.mockito.ArgumentCaptor; 51 import org.mockito.Mock; 52 import org.mockito.MockitoAnnotations; 53 54 @RunWith(AndroidJUnit4.class) 55 @SmallTest 56 public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { 57 58 private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"}; 59 private static final int[] DEFAULT_FOLDED_STATES = new int[]{0}; 60 private static final int[] DEFAULT_HALF_FOLDED_STATES = new int[]{2}; 61 private static final int[] DEFAULT_UNFOLDED_STATES = new int[]{1}; 62 63 @Mock private DeviceStateManager mDeviceStateManager; 64 @Mock private DeviceStateRotationLockSettingControllerLogger mLogger; 65 @Mock private DumpManager mDumpManager; 66 67 private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); 68 private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); 69 private final FakeRotationPolicy mFakeRotationPolicy = new FakeRotationPolicy(); 70 private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; 71 private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; 72 private DeviceStateRotationLockSettingsManager mSettingsManager; 73 private TestableContentResolver mContentResolver; 74 75 @Before setUp()76 public void setUp() { 77 MockitoAnnotations.initMocks(/* testClass= */ this); 78 TestableResources resources = mContext.getOrCreateTestableResources(); 79 resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS); 80 resources.addOverride(R.array.config_foldedDeviceStates, DEFAULT_FOLDED_STATES); 81 resources.addOverride(R.array.config_halfFoldedDeviceStates, DEFAULT_HALF_FOLDED_STATES); 82 resources.addOverride(R.array.config_openDeviceStates, DEFAULT_UNFOLDED_STATES); 83 84 ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor = 85 ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); 86 87 mContentResolver = mContext.getContentResolver(); 88 mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext); 89 mDeviceStateRotationLockSettingController = 90 new DeviceStateRotationLockSettingController( 91 mFakeRotationPolicy, 92 mDeviceStateManager, 93 mFakeExecutor, 94 mSettingsManager, 95 mLogger, 96 mDumpManager 97 ); 98 99 mDeviceStateRotationLockSettingController.setListening(true); 100 verify(mDeviceStateManager) 101 .registerCallback(any(), deviceStateCallbackArgumentCaptor.capture()); 102 mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue(); 103 } 104 105 @Test whenSavedSettingsEmpty_defaultsLoadedAndSaved()106 public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() { 107 initializeSettingsWith(); 108 109 assertThat( 110 Settings.Secure.getStringForUser( 111 mContentResolver, 112 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 113 UserHandle.USER_CURRENT)) 114 .isEqualTo("0:1:1:2:2:0"); 115 } 116 117 @Test whenNoSavedValueForDeviceState_assumeIgnored()118 public void whenNoSavedValueForDeviceState_assumeIgnored() { 119 initializeSettingsWith( 120 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); 121 mFakeRotationPolicy.setRotationLock(true); 122 123 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); 124 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 125 126 // Settings only exist for state 0 and 1 127 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); 128 129 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 130 } 131 132 @Test whenDeviceStateSwitched_loadCorrectSetting()133 public void whenDeviceStateSwitched_loadCorrectSetting() { 134 initializeSettingsWith( 135 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED); 136 mFakeRotationPolicy.setRotationLock(true); 137 138 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); 139 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 140 141 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); 142 assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); 143 } 144 145 @Test whenDeviceStateSwitched_settingIsIgnored_loadsDefaultFallbackSetting()146 public void whenDeviceStateSwitched_settingIsIgnored_loadsDefaultFallbackSetting() { 147 initializeSettingsWith(); 148 mFakeRotationPolicy.setRotationLock(true); 149 150 // State 2 -> Ignored -> Fall back to state 1 which is unlocked 151 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); 152 153 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 154 } 155 156 @Test whenDeviceStateSwitched_ignoredSetting_fallbackValueChanges_usesFallbackValue()157 public void whenDeviceStateSwitched_ignoredSetting_fallbackValueChanges_usesFallbackValue() { 158 initializeSettingsWith( 159 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 160 1, DEVICE_STATE_ROTATION_LOCK_LOCKED, 161 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); 162 mFakeRotationPolicy.setRotationLock(false); 163 164 // State 2 -> Ignored -> Fall back to state 1 which is locked 165 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); 166 167 assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); 168 } 169 170 @Test whenUserChangesSetting_saveSettingForCurrentState()171 public void whenUserChangesSetting_saveSettingForCurrentState() { 172 initializeSettingsWith( 173 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); 174 mSettingsManager.onPersistedSettingsChanged(); 175 mFakeRotationPolicy.setRotationLock(true); 176 177 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); 178 assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); 179 180 mDeviceStateRotationLockSettingController.onRotationLockStateChanged( 181 /* rotationLocked= */ false, /* affordanceVisible= */ true); 182 183 assertThat( 184 Settings.Secure.getStringForUser( 185 mContentResolver, 186 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 187 UserHandle.USER_CURRENT)) 188 .isEqualTo("0:2:1:2"); 189 } 190 191 @Test whenDeviceStateSwitchedToIgnoredState_useFallbackSetting()192 public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() { 193 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); 194 assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); 195 196 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); 197 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 198 } 199 200 @Test whenDeviceStateSwitchedToIgnoredState_noFallback_newSettingsSaveForPreviousState()201 public void whenDeviceStateSwitchedToIgnoredState_noFallback_newSettingsSaveForPreviousState() { 202 initializeSettingsWith( 203 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); 204 mFakeRotationPolicy.setRotationLock(true); 205 206 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); 207 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 208 209 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8)); 210 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 211 212 mDeviceStateRotationLockSettingController.onRotationLockStateChanged( 213 /* rotationLocked= */ true, /* affordanceVisible= */ true); 214 215 assertThat( 216 Settings.Secure.getStringForUser( 217 mContentResolver, 218 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 219 UserHandle.USER_CURRENT)) 220 .isEqualTo("1:1:8:0"); 221 } 222 223 @Test whenSettingsChangedExternally_updateRotationPolicy()224 public void whenSettingsChangedExternally_updateRotationPolicy() throws InterruptedException { 225 initializeSettingsWith( 226 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 227 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); 228 mFakeRotationPolicy.setRotationLock(false); 229 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); 230 231 assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); 232 233 // Changing device state 0 to LOCKED 234 initializeSettingsWith( 235 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); 236 237 assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); 238 } 239 240 @Test onRotationLockStateChanged_newSettingIsPersisted()241 public void onRotationLockStateChanged_newSettingIsPersisted() { 242 initializeSettingsWith( 243 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 244 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); 245 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); 246 247 mDeviceStateRotationLockSettingController.onRotationLockStateChanged( 248 /* rotationLocked= */ false, 249 /* affordanceVisible= */ true 250 ); 251 252 assertThat( 253 Settings.Secure.getStringForUser( 254 mContentResolver, 255 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 256 UserHandle.USER_CURRENT)) 257 .isEqualTo("0:2:1:2"); 258 } 259 260 @Test onRotationLockStateChanged_deviceStateIsIgnored_newSettingIsPersistedToFallback()261 public void onRotationLockStateChanged_deviceStateIsIgnored_newSettingIsPersistedToFallback() { 262 initializeSettingsWith( 263 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 264 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 265 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); 266 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); 267 268 mDeviceStateRotationLockSettingController.onRotationLockStateChanged( 269 /* rotationLocked= */ true, 270 /* affordanceVisible= */ true 271 ); 272 273 assertThat( 274 Settings.Secure.getStringForUser( 275 mContentResolver, 276 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 277 UserHandle.USER_CURRENT)) 278 .isEqualTo("0:1:1:1:2:0"); 279 } 280 281 @Test onRotationLockStateChange_stateIgnored_noFallback_settingIsPersistedToPrevious()282 public void onRotationLockStateChange_stateIgnored_noFallback_settingIsPersistedToPrevious() { 283 initializeSettingsWith( 284 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 285 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 286 8, DEVICE_STATE_ROTATION_LOCK_IGNORED); 287 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); 288 mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8)); 289 290 mDeviceStateRotationLockSettingController.onRotationLockStateChanged( 291 /* rotationLocked= */ true, 292 /* affordanceVisible= */ true 293 ); 294 295 assertThat( 296 Settings.Secure.getStringForUser( 297 mContentResolver, 298 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 299 UserHandle.USER_CURRENT)) 300 .isEqualTo("0:1:1:1:8:0"); 301 } 302 initializeSettingsWith(int... values)303 private void initializeSettingsWith(int... values) { 304 if (values.length % 2 != 0) { 305 throw new IllegalArgumentException("Expecting key-value pairs"); 306 } 307 StringBuilder sb = new StringBuilder(); 308 for (int i = 0; i < values.length; ) { 309 if (i > 0) { 310 sb.append(":"); 311 } 312 sb.append(values[i++]).append(":").append(values[i++]); 313 } 314 315 Settings.Secure.putStringForUser( 316 mContentResolver, 317 Settings.Secure.DEVICE_STATE_ROTATION_LOCK, 318 sb.toString(), 319 UserHandle.USER_CURRENT); 320 321 mSettingsManager.onPersistedSettingsChanged(); 322 } 323 createDeviceStateForIdentifier(int id)324 private DeviceState createDeviceStateForIdentifier(int id) { 325 return new DeviceState(new DeviceState.Configuration.Builder(id, "" /* name */).build()); 326 } 327 328 private static class FakeRotationPolicy implements RotationPolicyWrapper { 329 330 private boolean mRotationLock; 331 setRotationLock(boolean enabled)332 public void setRotationLock(boolean enabled) { 333 setRotationLock(enabled, /* caller= */ "FakeRotationPolicy"); 334 } 335 336 @Override setRotationLock(boolean enabled, String caller)337 public void setRotationLock(boolean enabled, String caller) { 338 mRotationLock = enabled; 339 } 340 setRotationLockAtAngle(boolean enabled, int rotation)341 public void setRotationLockAtAngle(boolean enabled, int rotation) { 342 setRotationLockAtAngle(enabled, rotation, /* caller= */ "FakeRotationPolicy"); 343 } 344 345 @Override setRotationLockAtAngle(boolean enabled, int rotation, String caller)346 public void setRotationLockAtAngle(boolean enabled, int rotation, String caller) { 347 mRotationLock = enabled; 348 } 349 350 @Override getRotationLockOrientation()351 public int getRotationLockOrientation() { 352 throw new AssertionError("Not implemented"); 353 } 354 355 @Override isRotationLockToggleVisible()356 public boolean isRotationLockToggleVisible() { 357 throw new AssertionError("Not implemented"); 358 } 359 360 @Override isRotationLocked()361 public boolean isRotationLocked() { 362 return mRotationLock; 363 } 364 365 @Override isCameraRotationEnabled()366 public boolean isCameraRotationEnabled() { 367 throw new AssertionError("Not implemented"); 368 } 369 370 @Override registerRotationPolicyListener( RotationPolicy.RotationPolicyListener listener, int userHandle)371 public void registerRotationPolicyListener( 372 RotationPolicy.RotationPolicyListener listener, int userHandle) { 373 throw new AssertionError("Not implemented"); 374 } 375 376 @Override unregisterRotationPolicyListener( RotationPolicy.RotationPolicyListener listener)377 public void unregisterRotationPolicyListener( 378 RotationPolicy.RotationPolicyListener listener) { 379 throw new AssertionError("Not implemented"); 380 } 381 } 382 } 383