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.server.wm; 18 19 import android.annotation.NonNull; 20 import android.provider.DeviceConfig; 21 import android.util.ArraySet; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.util.Map; 26 import java.util.concurrent.Executor; 27 28 /** 29 * Utility class that caches {@link DeviceConfig} flags for app compat features and listens 30 * to updates by implementing {@link DeviceConfig.OnPropertiesChangedListener}. 31 */ 32 final class LetterboxConfigurationDeviceConfig 33 implements DeviceConfig.OnPropertiesChangedListener { 34 35 static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT = "enable_compat_camera_treatment"; 36 private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true; 37 38 static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY = 39 "enable_display_rotation_immersive_app_compat_policy"; 40 private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY = 41 true; 42 43 static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST = 44 "allow_ignore_orientation_request"; 45 private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true; 46 47 static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus"; 48 private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true; 49 50 static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = 51 "enable_letterbox_translucent_activity"; 52 53 private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true; 54 55 static final String KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP = 56 "disable_size_compat_mode_after_orientation_change_from_app"; 57 private static final boolean 58 DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP = true; 59 60 @VisibleForTesting 61 static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of( 62 KEY_ENABLE_CAMERA_COMPAT_TREATMENT, 63 DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT, 64 KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY, 65 DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY, 66 KEY_ALLOW_IGNORE_ORIENTATION_REQUEST, 67 DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST, 68 KEY_ENABLE_COMPAT_FAKE_FOCUS, 69 DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS, 70 KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY, 71 DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY, 72 KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP, 73 DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP 74 ); 75 76 // Whether camera compatibility treatment is enabled. 77 // See DisplayRotationCompatPolicy for context. 78 private boolean mIsCameraCompatTreatmentEnabled = 79 DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT; 80 81 // Whether enabling rotation compat policy for immersive apps that prevents auto rotation 82 // into non-optimal screen orientation while in fullscreen. This is needed because immersive 83 // apps, such as games, are often not optimized for all orientations and can have a poor UX 84 // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger 85 // such rotations accidentally when auto rotation is on. 86 private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled = 87 DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY; 88 89 // Whether enabling ignoreOrientationRequest is allowed on the device. 90 private boolean mIsAllowIgnoreOrientationRequest = 91 DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST; 92 93 // Whether sending compat fake focus for split screen resumed activities is enabled. This is 94 // needed because some game engines wait to get focus before drawing the content of the app 95 // which isn't guaranteed by default in multi-window modes. 96 private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS; 97 98 // Whether the letterbox strategy for transparent activities is allowed 99 private boolean mIsTranslucentLetterboxingAllowed = 100 DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY; 101 102 // Whether size compat mode is disabled after an orientation change request comes from the app 103 private boolean mIsSizeCompatModeDisabledAfterOrientationChangeFromApp = 104 DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP; 105 106 // Set of active device configs that need to be updated in 107 // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged. 108 private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>(); 109 LetterboxConfigurationDeviceConfig(@onNull final Executor executor)110 LetterboxConfigurationDeviceConfig(@NonNull final Executor executor) { 111 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, 112 executor, /* onPropertiesChangedListener */ this); 113 } 114 115 @Override onPropertiesChanged(@onNull final DeviceConfig.Properties properties)116 public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) { 117 for (int i = mActiveDeviceConfigsSet.size() - 1; i >= 0; i--) { 118 String key = mActiveDeviceConfigsSet.valueAt(i); 119 // Reads the new configuration, if the device config properties contain the key. 120 if (properties.getKeyset().contains(key)) { 121 readAndSaveValueFromDeviceConfig(key); 122 } 123 } 124 } 125 126 /** 127 * Adds {@code key} to a set of flags that can be updated from the server if 128 * {@code isActive} is {@code true} and read it's current value from {@link DeviceConfig}. 129 */ updateFlagActiveStatus(boolean isActive, String key)130 void updateFlagActiveStatus(boolean isActive, String key) { 131 if (!isActive) { 132 return; 133 } 134 mActiveDeviceConfigsSet.add(key); 135 readAndSaveValueFromDeviceConfig(key); 136 } 137 138 /** 139 * Returns values of the {@code key} flag. 140 * 141 * @throws AssertionError {@code key} isn't recognised. 142 */ getFlag(String key)143 boolean getFlag(String key) { 144 switch (key) { 145 case KEY_ENABLE_CAMERA_COMPAT_TREATMENT: 146 return mIsCameraCompatTreatmentEnabled; 147 case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY: 148 return mIsDisplayRotationImmersiveAppCompatPolicyEnabled; 149 case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST: 150 return mIsAllowIgnoreOrientationRequest; 151 case KEY_ENABLE_COMPAT_FAKE_FOCUS: 152 return mIsCompatFakeFocusAllowed; 153 case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY: 154 return mIsTranslucentLetterboxingAllowed; 155 case KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP: 156 return mIsSizeCompatModeDisabledAfterOrientationChangeFromApp; 157 default: 158 throw new AssertionError("Unexpected flag name: " + key); 159 } 160 } 161 readAndSaveValueFromDeviceConfig(String key)162 private void readAndSaveValueFromDeviceConfig(String key) { 163 Boolean defaultValue = sKeyToDefaultValueMap.get(key); 164 if (defaultValue == null) { 165 throw new AssertionError("Haven't found default value for flag: " + key); 166 } 167 switch (key) { 168 case KEY_ENABLE_CAMERA_COMPAT_TREATMENT: 169 mIsCameraCompatTreatmentEnabled = getDeviceConfig(key, defaultValue); 170 break; 171 case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY: 172 mIsDisplayRotationImmersiveAppCompatPolicyEnabled = 173 getDeviceConfig(key, defaultValue); 174 break; 175 case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST: 176 mIsAllowIgnoreOrientationRequest = getDeviceConfig(key, defaultValue); 177 break; 178 case KEY_ENABLE_COMPAT_FAKE_FOCUS: 179 mIsCompatFakeFocusAllowed = getDeviceConfig(key, defaultValue); 180 break; 181 case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY: 182 mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue); 183 break; 184 case KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP: 185 mIsSizeCompatModeDisabledAfterOrientationChangeFromApp = 186 getDeviceConfig(key, defaultValue); 187 break; 188 default: 189 throw new AssertionError("Unexpected flag name: " + key); 190 } 191 } 192 getDeviceConfig(String key, boolean defaultValue)193 private boolean getDeviceConfig(String key, boolean defaultValue) { 194 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, 195 key, defaultValue); 196 } 197 } 198