1 /* 2 * Copyright (C) 2020 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 android.hardware.devicestate; 18 19 import android.Manifest; 20 import android.annotation.CallbackExecutor; 21 import android.annotation.FlaggedApi; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SuppressLint; 26 import android.annotation.SystemApi; 27 import android.annotation.SystemService; 28 import android.annotation.TestApi; 29 import android.content.Context; 30 31 import com.android.internal.util.ArrayUtils; 32 33 import java.util.List; 34 import java.util.concurrent.Executor; 35 import java.util.function.Consumer; 36 37 /** 38 * Manages the state of the system for devices with user-configurable hardware like a foldable 39 * phone. 40 * 41 * @hide 42 */ 43 @SystemApi 44 @FlaggedApi(android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_API) 45 @SystemService(Context.DEVICE_STATE_SERVICE) 46 public final class DeviceStateManager { 47 /** 48 * Invalid device state. 49 * 50 * @hide 51 */ 52 @TestApi 53 public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; 54 55 /** 56 * The minimum allowed device state identifier. 57 * @hide 58 */ 59 @TestApi 60 public static final int MINIMUM_DEVICE_STATE_IDENTIFIER = 0; 61 62 /** 63 * The maximum allowed device state identifier. 64 * @hide 65 */ 66 @TestApi 67 public static final int MAXIMUM_DEVICE_STATE_IDENTIFIER = 10000; 68 69 /** 70 * Intent needed to launch the rear display overlay activity from SysUI 71 * 72 * @hide 73 */ 74 public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY = 75 "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY"; 76 77 /** 78 * Intent extra sent to the rear display overlay activity of the current base state 79 * 80 * @hide 81 */ 82 public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE = 83 "original_device_base_state"; 84 85 private final DeviceStateManagerGlobal mGlobal; 86 87 /** @hide */ DeviceStateManager()88 public DeviceStateManager() { 89 DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance(); 90 if (global == null) { 91 throw new IllegalStateException( 92 "Failed to get instance of global device state manager."); 93 } 94 mGlobal = global; 95 } 96 97 /** 98 * Returns the list of device states that are supported and can be requested with 99 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 100 */ 101 @NonNull getSupportedDeviceStates()102 public List<DeviceState> getSupportedDeviceStates() { 103 return mGlobal.getSupportedDeviceStates(); 104 } 105 106 /** 107 * Submits a {@link DeviceStateRequest request} to modify the device state. 108 * <p> 109 * By default, the request is kept active until one of the following occurs: 110 * <ul> 111 * <li>The system deems the request can no longer be honored, for example if the requested 112 * state becomes unsupported. 113 * <li>A call to {@link #cancelStateRequest}. 114 * <li>Another processes submits a request succeeding this request in which case the request 115 * will be canceled. 116 * </ul> 117 * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. 118 * 119 * @throws IllegalArgumentException if the requested state is unsupported. 120 * @throws SecurityException if the caller is neither the current top-focused activity nor if 121 * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. 122 * 123 * @see DeviceStateRequest 124 * @hide 125 */ 126 @SuppressLint("RequiresPermission") // Lint doesn't handle conditional permission checks today 127 @TestApi 128 @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, 129 conditional = true) requestState(@onNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback)130 public void requestState(@NonNull DeviceStateRequest request, 131 @Nullable @CallbackExecutor Executor executor, 132 @Nullable DeviceStateRequest.Callback callback) { 133 mGlobal.requestState(request, executor, callback); 134 } 135 136 /** 137 * Cancels the active {@link DeviceStateRequest} previously submitted with a call to 138 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 139 * <p> 140 * This method is noop if there is no request currently active. 141 * 142 * @throws SecurityException if the caller is neither the current top-focused activity nor if 143 * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. 144 * @hide 145 */ 146 @SuppressLint("RequiresPermission") // Lint doesn't handle conditional permission checks today 147 @TestApi 148 @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, 149 conditional = true) cancelStateRequest()150 public void cancelStateRequest() { 151 mGlobal.cancelStateRequest(); 152 } 153 154 /** 155 * Submits a {@link DeviceStateRequest request} to override the base state of the device. This 156 * should only be used for testing, where you want to simulate the physical change to the 157 * device state. 158 * <p> 159 * By default, the request is kept active until one of the following occurs: 160 * <ul> 161 * <li>The physical state of the device changes</li> 162 * <li>The system deems the request can no longer be honored, for example if the requested 163 * state becomes unsupported. 164 * <li>A call to {@link #cancelBaseStateOverride}. 165 * <li>Another processes submits a request succeeding this request in which case the request 166 * will be canceled. 167 * </ul> 168 * 169 * Submitting a base state override request may not cause any change in the presentation 170 * of the system if there is an emulated request made through {@link #requestState}, as the 171 * emulated override requests take priority. 172 * 173 * @throws IllegalArgumentException if the requested state is unsupported. 174 * 175 * @see DeviceStateRequest 176 * @hide 177 */ 178 @TestApi 179 @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) requestBaseStateOverride(@onNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback)180 public void requestBaseStateOverride(@NonNull DeviceStateRequest request, 181 @Nullable @CallbackExecutor Executor executor, 182 @Nullable DeviceStateRequest.Callback callback) { 183 mGlobal.requestBaseStateOverride(request, executor, callback); 184 } 185 186 /** 187 * Cancels the active {@link DeviceStateRequest} previously submitted with a call to 188 * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 189 * <p> 190 * This method is noop if there is no base state request currently active. 191 * 192 * @hide 193 */ 194 @TestApi 195 @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE) cancelBaseStateOverride()196 public void cancelBaseStateOverride() { 197 mGlobal.cancelBaseStateOverride(); 198 } 199 200 /** 201 * Registers a callback to receive notifications about changes in device state. 202 * 203 * @param executor the executor to process notifications. 204 * @param callback the callback to register. 205 * 206 * @see DeviceStateCallback 207 */ registerCallback(@onNull @allbackExecutor Executor executor, @NonNull DeviceStateCallback callback)208 public void registerCallback(@NonNull @CallbackExecutor Executor executor, 209 @NonNull DeviceStateCallback callback) { 210 mGlobal.registerDeviceStateCallback(callback, executor); 211 } 212 213 /** 214 * Unregisters a callback previously registered with 215 * {@link #registerCallback(Executor, DeviceStateCallback)}. 216 */ unregisterCallback(@onNull DeviceStateCallback callback)217 public void unregisterCallback(@NonNull DeviceStateCallback callback) { 218 mGlobal.unregisterDeviceStateCallback(callback); 219 } 220 221 /** Callback to receive notifications about changes in device state. */ 222 public interface DeviceStateCallback { 223 /** 224 * Called in response to a change in the states supported by the device. 225 * <p> 226 * Guaranteed to be called once on registration of the callback with the initial value and 227 * then on every subsequent change in the supported states. 228 * 229 * The supported device states may change due to certain states becoming unavailable 230 * due to device configuration or device conditions such as if the device is too hot or 231 * external monitors have been connected. 232 * 233 * @param supportedStates the new supported states. 234 * 235 * @see DeviceStateManager#getSupportedDeviceStates() 236 */ onSupportedStatesChanged(@onNull List<DeviceState> supportedStates)237 default void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {} 238 239 /** 240 * Called in response to device state changes. 241 * <p> 242 * Guaranteed to be called once on registration of the callback with the initial value and 243 * then on every subsequent change in device state. 244 * 245 * @param state the new device state. 246 */ onDeviceStateChanged(@onNull DeviceState state)247 void onDeviceStateChanged(@NonNull DeviceState state); 248 } 249 250 /** 251 * Listens to changes in device state and reports the state as folded if the device state 252 * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState} 253 * resource. 254 * @hide 255 */ 256 public static class FoldStateListener implements DeviceStateCallback { 257 private final int[] mFoldedDeviceStates; 258 private final Consumer<Boolean> mDelegate; 259 private final android.hardware.devicestate.feature.flags.FeatureFlags mFeatureFlags; 260 261 @Nullable 262 private Boolean lastResult; 263 FoldStateListener(Context context)264 public FoldStateListener(Context context) { 265 this(context, folded -> {}); 266 } 267 FoldStateListener(Context context, Consumer<Boolean> listener)268 public FoldStateListener(Context context, Consumer<Boolean> listener) { 269 mFoldedDeviceStates = context.getResources().getIntArray( 270 com.android.internal.R.array.config_foldedDeviceStates); 271 mDelegate = listener; 272 mFeatureFlags = new android.hardware.devicestate.feature.flags.FeatureFlagsImpl(); 273 } 274 275 @Override onDeviceStateChanged(@onNull DeviceState deviceState)276 public final void onDeviceStateChanged(@NonNull DeviceState deviceState) { 277 final boolean folded; 278 if (mFeatureFlags.deviceStatePropertyApi()) { 279 // TODO(b/325124054): Update when system server refactor is completed 280 folded = deviceState.hasProperty( 281 DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) 282 || ArrayUtils.contains(mFoldedDeviceStates, deviceState.getIdentifier()); 283 } else { 284 folded = ArrayUtils.contains(mFoldedDeviceStates, deviceState.getIdentifier()); 285 } 286 287 if (lastResult == null || !lastResult.equals(folded)) { 288 lastResult = folded; 289 mDelegate.accept(folded); 290 } 291 } 292 293 @Nullable getFolded()294 public Boolean getFolded() { 295 return lastResult; 296 } 297 } 298 } 299