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