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