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