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