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