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.annotation.CallbackExecutor; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SystemService; 24 import android.annotation.TestApi; 25 import android.content.Context; 26 27 import com.android.internal.util.ArrayUtils; 28 29 import java.util.concurrent.Executor; 30 import java.util.function.Consumer; 31 32 /** 33 * Manages the state of the system for devices with user-configurable hardware like a foldable 34 * phone. 35 * 36 * @hide 37 */ 38 @TestApi 39 @SystemService(Context.DEVICE_STATE_SERVICE) 40 public final class DeviceStateManager { 41 /** 42 * Invalid device state. 43 * 44 * @hide 45 */ 46 public static final int INVALID_DEVICE_STATE = -1; 47 48 /** The minimum allowed device state identifier. */ 49 public static final int MINIMUM_DEVICE_STATE = 0; 50 51 /** The maximum allowed device state identifier. */ 52 public static final int MAXIMUM_DEVICE_STATE = 255; 53 54 private final DeviceStateManagerGlobal mGlobal; 55 56 /** @hide */ DeviceStateManager()57 public DeviceStateManager() { 58 DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance(); 59 if (global == null) { 60 throw new IllegalStateException( 61 "Failed to get instance of global device state manager."); 62 } 63 mGlobal = global; 64 } 65 66 /** 67 * Returns the list of device states that are supported and can be requested with 68 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 69 */ 70 @NonNull getSupportedStates()71 public int[] getSupportedStates() { 72 return mGlobal.getSupportedStates(); 73 } 74 75 /** 76 * Submits a {@link DeviceStateRequest request} to modify the device state. 77 * <p> 78 * By default, the request is kept active until one of the following occurs: 79 * <ul> 80 * <li>The system deems the request can no longer be honored, for example if the requested 81 * state becomes unsupported. 82 * <li>A call to {@link #cancelRequest(DeviceStateRequest)}. 83 * <li>Another processes submits a request succeeding this request in which case the request 84 * will be suspended until the interrupting request is canceled. 85 * </ul> 86 * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. 87 * 88 * @throws IllegalArgumentException if the requested state is unsupported. 89 * @throws SecurityException if the caller is neither the current top-focused activity nor if 90 * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. 91 * 92 * @see DeviceStateRequest 93 */ 94 @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, 95 conditional = true) requestState(@onNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback)96 public void requestState(@NonNull DeviceStateRequest request, 97 @Nullable @CallbackExecutor Executor executor, 98 @Nullable DeviceStateRequest.Callback callback) { 99 mGlobal.requestState(request, executor, callback); 100 } 101 102 /** 103 * Cancels a {@link DeviceStateRequest request} previously submitted with a call to 104 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 105 * <p> 106 * This method is noop if the {@code request} has not been submitted with a call to 107 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 108 * 109 * @throws SecurityException if the caller is neither the current top-focused activity nor if 110 * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. 111 */ 112 @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, 113 conditional = true) cancelRequest(@onNull DeviceStateRequest request)114 public void cancelRequest(@NonNull DeviceStateRequest request) { 115 mGlobal.cancelRequest(request); 116 } 117 118 /** 119 * Registers a callback to receive notifications about changes in device state. 120 * 121 * @param executor the executor to process notifications. 122 * @param callback the callback to register. 123 * 124 * @see DeviceStateCallback 125 */ registerCallback(@onNull @allbackExecutor Executor executor, @NonNull DeviceStateCallback callback)126 public void registerCallback(@NonNull @CallbackExecutor Executor executor, 127 @NonNull DeviceStateCallback callback) { 128 mGlobal.registerDeviceStateCallback(callback, executor); 129 } 130 131 /** 132 * Unregisters a callback previously registered with 133 * {@link #registerCallback(Executor, DeviceStateCallback)}. 134 */ unregisterCallback(@onNull DeviceStateCallback callback)135 public void unregisterCallback(@NonNull DeviceStateCallback callback) { 136 mGlobal.unregisterDeviceStateCallback(callback); 137 } 138 139 /** Callback to receive notifications about changes in device state. */ 140 public interface DeviceStateCallback { 141 /** 142 * Called in response to a change in the states supported by the device. 143 * <p> 144 * Guaranteed to be called once on registration of the callback with the initial value and 145 * then on every subsequent change in the supported states. 146 * 147 * @param supportedStates the new supported states. 148 * 149 * @see DeviceStateManager#getSupportedStates() 150 */ onSupportedStatesChanged(@onNull int[] supportedStates)151 default void onSupportedStatesChanged(@NonNull int[] supportedStates) {} 152 153 /** 154 * Called in response to a change in the base device state. 155 * <p> 156 * The base state is the state of the device without considering any requests made through 157 * calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)} 158 * from any client process. The base state is guaranteed to match the state provided with a 159 * call to {@link #onStateChanged(int)} when there are no active requests from any process. 160 * <p> 161 * Guaranteed to be called once on registration of the callback with the initial value and 162 * then on every subsequent change in the non-override state. 163 * 164 * @param state the new base device state. 165 */ onBaseStateChanged(int state)166 default void onBaseStateChanged(int state) {} 167 168 /** 169 * Called in response to device state changes. 170 * <p> 171 * Guaranteed to be called once on registration of the callback with the initial value and 172 * then on every subsequent change in device state. 173 * 174 * @param state the new device state. 175 */ onStateChanged(int state)176 void onStateChanged(int state); 177 } 178 179 /** 180 * Listens to changes in device state and reports the state as folded if the device state 181 * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState} 182 * resource. 183 * @hide 184 */ 185 public static class FoldStateListener implements DeviceStateCallback { 186 private final int[] mFoldedDeviceStates; 187 private final Consumer<Boolean> mDelegate; 188 189 @Nullable 190 private Boolean lastResult; 191 FoldStateListener(Context context, Consumer<Boolean> listener)192 public FoldStateListener(Context context, Consumer<Boolean> listener) { 193 mFoldedDeviceStates = context.getResources().getIntArray( 194 com.android.internal.R.array.config_foldedDeviceStates); 195 mDelegate = listener; 196 } 197 198 @Override onStateChanged(int state)199 public final void onStateChanged(int state) { 200 final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); 201 202 if (lastResult == null || !lastResult.equals(folded)) { 203 lastResult = folded; 204 mDelegate.accept(folded); 205 } 206 } 207 } 208 } 209