• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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