• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.settingslib.devicestate;
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 android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.database.ContentObserver;
28 import android.hardware.devicestate.DeviceStateManager;
29 import android.os.Build;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.text.TextUtils;
35 import android.util.IndentingPrintWriter;
36 import android.util.Log;
37 import android.util.SparseIntArray;
38 
39 import com.android.internal.R;
40 import com.android.internal.annotations.VisibleForTesting;
41 
42 import java.io.PrintWriter;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Set;
48 
49 /**
50  * Manages device-state based rotation lock settings. Handles reading, writing, and listening for
51  * changes.
52  */
53 public final class DeviceStateRotationLockSettingsManager implements
54         DeviceStateAutoRotateSettingManager {
55 
56     private static final String TAG = "DSRotLockSettingsMngr";
57     private static final String SEPARATOR_REGEX = ":";
58 
59     private static DeviceStateRotationLockSettingsManager sSingleton;
60 
61     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
62     private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>();
63     private final SecureSettings mSecureSettings;
64     private final PosturesHelper mPosturesHelper;
65     private String[] mPostureRotationLockDefaults;
66     private SparseIntArray mPostureRotationLockSettings;
67     private SparseIntArray mPostureDefaultRotationLockSettings;
68     private SparseIntArray mPostureRotationLockFallbackSettings;
69     private List<SettableDeviceState> mSettableDeviceStates;
70 
DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings)71     public DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
72         mSecureSettings = secureSettings;
73 
74         mPosturesHelper = new PosturesHelper(context, getDeviceStateManager(context));
75         mPostureRotationLockDefaults =
76                 context.getResources()
77                         .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
78         loadDefaults();
79         initializeInMemoryMap();
80         listenForSettingsChange();
81     }
82 
83     @Nullable
getDeviceStateManager(Context context)84     private DeviceStateManager getDeviceStateManager(Context context) {
85         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
86             return context.getSystemService(DeviceStateManager.class);
87         }
88         return null;
89     }
90 
listenForSettingsChange()91     private void listenForSettingsChange() {
92         mSecureSettings
93                 .registerContentObserver(
94                         Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
95                         /* notifyForDescendants= */ false,
96                         new ContentObserver(mMainHandler) {
97                             @Override
98                             public void onChange(boolean selfChange) {
99                                 onPersistedSettingsChanged();
100                             }
101                         },
102                         UserHandle.USER_CURRENT);
103     }
104 
105     /**
106      * Registers a {@link DeviceStateAutoRotateSettingListener} to be notified when the settings
107      * change. Can be called multiple times with different listeners.
108      */
109     @Override
registerListener(@onNull DeviceStateAutoRotateSettingListener runnable)110     public void registerListener(@NonNull DeviceStateAutoRotateSettingListener runnable) {
111         mListeners.add(runnable);
112     }
113 
114     /**
115      * Unregisters a {@link DeviceStateAutoRotateSettingListener}. No-op if the given instance
116      * was never registered.
117      */
118     @Override
unregisterListener( @onNull DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener)119     public void unregisterListener(
120             @NonNull DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) {
121         if (!mListeners.remove(deviceStateAutoRotateSettingListener)) {
122             Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
123         }
124     }
125 
126     /** Updates the rotation lock setting for a specified device state. */
127     @Override
updateSetting(int deviceState, boolean rotationLocked)128     public void updateSetting(int deviceState, boolean rotationLocked) {
129         int posture = mPosturesHelper.deviceStateToPosture(deviceState);
130         if (mPostureRotationLockFallbackSettings.indexOfKey(posture) >= 0) {
131             // The setting for this device posture is IGNORED, and has a fallback posture.
132             // The setting for that fallback posture should be the changed in this case.
133             posture = mPostureRotationLockFallbackSettings.get(posture);
134         }
135         mPostureRotationLockSettings.put(
136                 posture,
137                 rotationLocked
138                         ? DEVICE_STATE_ROTATION_LOCK_LOCKED
139                         : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
140         persistSettings();
141     }
142 
143     /**
144      * Returns the {@link Settings.Secure.DeviceStateRotationLockSetting} for the given device
145      * state.
146      *
147      * <p>If the setting for this device state is {@link DEVICE_STATE_ROTATION_LOCK_IGNORED}, it
148      * will return the setting for the fallback device state.
149      *
150      * <p>If no fallback is specified for this device state, it will return {@link
151      * DEVICE_STATE_ROTATION_LOCK_IGNORED}.
152      */
153     @Settings.Secure.DeviceStateRotationLockSetting
154     @Override
getRotationLockSetting(int deviceState)155     public int getRotationLockSetting(int deviceState) {
156         int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState);
157         int rotationLockSetting = mPostureRotationLockSettings.get(
158                 devicePosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
159         if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
160             rotationLockSetting = getFallbackRotationLockSetting(devicePosture);
161         }
162         return rotationLockSetting;
163     }
164 
getFallbackRotationLockSetting(int devicePosture)165     private int getFallbackRotationLockSetting(int devicePosture) {
166         int indexOfFallback = mPostureRotationLockFallbackSettings.indexOfKey(devicePosture);
167         if (indexOfFallback < 0) {
168             Log.w(TAG, "Setting is ignored, but no fallback was specified.");
169             return DEVICE_STATE_ROTATION_LOCK_IGNORED;
170         }
171         int fallbackPosture = mPostureRotationLockFallbackSettings.valueAt(indexOfFallback);
172         return mPostureRotationLockSettings.get(fallbackPosture,
173                 /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
174     }
175 
176 
177     /** Returns true if the rotation is locked for the current device state */
178     @Override
isRotationLocked(int deviceState)179     public boolean isRotationLocked(int deviceState) {
180         return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
181     }
182 
183     /**
184      * Returns true if there is no device state for which the current setting is {@link
185      * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
186      */
187     @Override
isRotationLockedForAllStates()188     public boolean isRotationLockedForAllStates() {
189         for (int i = 0; i < mPostureRotationLockSettings.size(); i++) {
190             if (mPostureRotationLockSettings.valueAt(i)
191                     == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) {
192                 return false;
193             }
194         }
195         return true;
196     }
197 
198     /** Returns a list of device states and their respective auto-rotation setting availability. */
199     @Override
200     @NonNull
getSettableDeviceStates()201     public List<SettableDeviceState> getSettableDeviceStates() {
202         // Returning a copy to make sure that nothing outside can mutate our internal list.
203         return new ArrayList<>(mSettableDeviceStates);
204     }
205 
initializeInMemoryMap()206     private void initializeInMemoryMap() {
207         String serializedSetting = getPersistedSettingValue();
208         if (TextUtils.isEmpty(serializedSetting)) {
209             // No settings saved, we should load the defaults and persist them.
210             fallbackOnDefaults();
211             return;
212         }
213         String[] values = serializedSetting.split(SEPARATOR_REGEX);
214         if (values.length % 2 != 0) {
215             // Each entry should be a key/value pair, so this is corrupt.
216             Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
217             fallbackOnDefaults();
218             return;
219         }
220         mPostureRotationLockSettings = new SparseIntArray(values.length / 2);
221         int key;
222         int value;
223 
224         for (int i = 0; i < values.length - 1; ) {
225             try {
226                 key = Integer.parseInt(values[i++]);
227                 value = Integer.parseInt(values[i++]);
228                 boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
229                 boolean isDefaultValueIgnored = mPostureDefaultRotationLockSettings.get(key)
230                         == DEVICE_STATE_ROTATION_LOCK_IGNORED;
231                 if (isPersistedValueIgnored != isDefaultValueIgnored) {
232                     Log.w(TAG, "Conflict for ignored device state " + key
233                             + ". Falling back on defaults");
234                     fallbackOnDefaults();
235                     return;
236                 }
237                 mPostureRotationLockSettings.put(key, value);
238             } catch (NumberFormatException e) {
239                 Log.wtf(TAG, "Error deserializing one of the saved settings", e);
240                 fallbackOnDefaults();
241                 return;
242             }
243         }
244     }
245 
246     /**
247      * Resets the state of the class and saved settings back to the default values provided by the
248      * resources config.
249      */
250     @VisibleForTesting
resetStateForTesting(Resources resources)251     public void resetStateForTesting(Resources resources) {
252         mPostureRotationLockDefaults =
253                 resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
254         fallbackOnDefaults();
255     }
256 
fallbackOnDefaults()257     private void fallbackOnDefaults() {
258         loadDefaults();
259         persistSettings();
260     }
261 
persistSettings()262     private void persistSettings() {
263         if (mPostureRotationLockSettings.size() == 0) {
264             persistSettingIfChanged(/* newSettingValue= */ "");
265             return;
266         }
267 
268         StringBuilder stringBuilder = new StringBuilder();
269         stringBuilder
270                 .append(mPostureRotationLockSettings.keyAt(0))
271                 .append(SEPARATOR_REGEX)
272                 .append(mPostureRotationLockSettings.valueAt(0));
273 
274         for (int i = 1; i < mPostureRotationLockSettings.size(); i++) {
275             stringBuilder
276                     .append(SEPARATOR_REGEX)
277                     .append(mPostureRotationLockSettings.keyAt(i))
278                     .append(SEPARATOR_REGEX)
279                     .append(mPostureRotationLockSettings.valueAt(i));
280         }
281         persistSettingIfChanged(stringBuilder.toString());
282     }
283 
persistSettingIfChanged(String newSettingValue)284     private void persistSettingIfChanged(String newSettingValue) {
285         String lastSettingValue = getPersistedSettingValue();
286         Log.v(TAG, "persistSettingIfChanged: "
287                 + "last=" + lastSettingValue + ", "
288                 + "new=" + newSettingValue);
289         if (TextUtils.equals(lastSettingValue, newSettingValue)) {
290             return;
291         }
292         mSecureSettings.putStringForUser(
293                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
294                 /* value= */ newSettingValue,
295                 UserHandle.USER_CURRENT);
296     }
297 
getPersistedSettingValue()298     private String getPersistedSettingValue() {
299         return mSecureSettings.getStringForUser(
300                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
301                 UserHandle.USER_CURRENT);
302     }
303 
loadDefaults()304     private void loadDefaults() {
305         mSettableDeviceStates = new ArrayList<>(mPostureRotationLockDefaults.length);
306         mPostureDefaultRotationLockSettings = new SparseIntArray(
307                 mPostureRotationLockDefaults.length);
308         mPostureRotationLockSettings = new SparseIntArray(mPostureRotationLockDefaults.length);
309         mPostureRotationLockFallbackSettings = new SparseIntArray(1);
310         for (String entry : mPostureRotationLockDefaults) {
311             String[] values = entry.split(SEPARATOR_REGEX);
312             try {
313                 int posture = Integer.parseInt(values[0]);
314                 int rotationLockSetting = Integer.parseInt(values[1]);
315                 if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
316                     if (values.length == 3) {
317                         int fallbackPosture = Integer.parseInt(values[2]);
318                         mPostureRotationLockFallbackSettings.put(posture, fallbackPosture);
319                     } else {
320                         Log.w(TAG,
321                                 "Rotation lock setting is IGNORED, but values have unexpected "
322                                         + "size of "
323                                         + values.length);
324                     }
325                 }
326                 boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
327                 Integer deviceState = mPosturesHelper.postureToDeviceState(posture);
328                 if (deviceState != null) {
329                     mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
330                 } else {
331                     Log.wtf(TAG, "No matching device state for posture: " + posture);
332                 }
333                 mPostureRotationLockSettings.put(posture, rotationLockSetting);
334                 mPostureDefaultRotationLockSettings.put(posture, rotationLockSetting);
335             } catch (NumberFormatException e) {
336                 Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
337                 return;
338             }
339         }
340     }
341 
342     @Override
dump(@onNull PrintWriter writer, String[] args)343     public void dump(@NonNull PrintWriter writer, String[] args) {
344         IndentingPrintWriter indentingWriter = new IndentingPrintWriter(writer);
345         indentingWriter.println("DeviceStateRotationLockSettingsManager");
346         indentingWriter.increaseIndent();
347         indentingWriter.println("mPostureRotationLockDefaults: "
348                 + Arrays.toString(mPostureRotationLockDefaults));
349         indentingWriter.println(
350                 "mPostureDefaultRotationLockSettings: " + mPostureDefaultRotationLockSettings);
351         indentingWriter.println(
352                 "mDeviceStateRotationLockSettings: " + mPostureRotationLockSettings);
353         indentingWriter.println(
354                 "mPostureRotationLockFallbackSettings: " + mPostureRotationLockFallbackSettings);
355         indentingWriter.println("mSettableDeviceStates: " + mSettableDeviceStates);
356         indentingWriter.decreaseIndent();
357     }
358 
359     /**
360      * Called when the persisted settings have changed, requiring a reinitialization of the
361      * in-memory map.
362      */
363     @VisibleForTesting
onPersistedSettingsChanged()364     public void onPersistedSettingsChanged() {
365         initializeInMemoryMap();
366         notifyListeners();
367     }
368 
notifyListeners()369     private void notifyListeners() {
370         for (DeviceStateAutoRotateSettingListener r : mListeners) {
371             r.onSettingsChanged();
372         }
373     }
374 }
375