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