• 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      * Intent needed to launch the rear display overlay activity from SysUI
71      *
72      * @hide
73      */
74     public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY =
75             "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY";
76 
77     /**
78      * Intent extra sent to the rear display overlay activity of the current base state
79      *
80      * @hide
81      */
82     public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE =
83             "original_device_base_state";
84 
85     private final DeviceStateManagerGlobal mGlobal;
86 
87     /** @hide */
DeviceStateManager()88     public DeviceStateManager() {
89         DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance();
90         if (global == null) {
91             throw new IllegalStateException(
92                     "Failed to get instance of global device state manager.");
93         }
94         mGlobal = global;
95     }
96 
97     /**
98      * Returns the list of device states that are supported and can be requested with
99      * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
100      */
101     @NonNull
getSupportedDeviceStates()102     public List<DeviceState> getSupportedDeviceStates() {
103         return mGlobal.getSupportedDeviceStates();
104     }
105 
106     /**
107      * Submits a {@link DeviceStateRequest request} to modify the device state.
108      * <p>
109      * By default, the request is kept active until one of the following occurs:
110      * <ul>
111      *     <li>The system deems the request can no longer be honored, for example if the requested
112      *     state becomes unsupported.
113      *     <li>A call to {@link #cancelStateRequest}.
114      *     <li>Another processes submits a request succeeding this request in which case the request
115      *     will be canceled.
116      * </ul>
117      * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
118      *
119      * @throws IllegalArgumentException if the requested state is unsupported.
120      * @throws SecurityException if the caller is neither the current top-focused activity nor if
121      * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
122      *
123      * @see DeviceStateRequest
124      * @hide
125      */
126     @SuppressLint("RequiresPermission") // Lint doesn't handle conditional permission checks today
127     @TestApi
128     @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
129             conditional = true)
requestState(@onNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback)130     public void requestState(@NonNull DeviceStateRequest request,
131             @Nullable @CallbackExecutor Executor executor,
132             @Nullable DeviceStateRequest.Callback callback) {
133         mGlobal.requestState(request, executor, callback);
134     }
135 
136     /**
137      * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
138      * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
139      * <p>
140      * This method is noop if there is no request currently active.
141      *
142      * @throws SecurityException if the caller is neither the current top-focused activity nor if
143      * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
144      * @hide
145      */
146     @SuppressLint("RequiresPermission") // Lint doesn't handle conditional permission checks today
147     @TestApi
148     @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
149             conditional = true)
cancelStateRequest()150     public void cancelStateRequest() {
151         mGlobal.cancelStateRequest();
152     }
153 
154     /**
155      * Submits a {@link DeviceStateRequest request} to override the base state of the device. This
156      * should only be used for testing, where you want to simulate the physical change to the
157      * device state.
158      * <p>
159      * By default, the request is kept active until one of the following occurs:
160      * <ul>
161      *     <li>The physical state of the device changes</li>
162      *     <li>The system deems the request can no longer be honored, for example if the requested
163      *     state becomes unsupported.
164      *     <li>A call to {@link #cancelBaseStateOverride}.
165      *     <li>Another processes submits a request succeeding this request in which case the request
166      *     will be canceled.
167      * </ul>
168      *
169      * Submitting a base state override request may not cause any change in the presentation
170      * of the system if there is an emulated request made through {@link #requestState}, as the
171      * emulated override requests take priority.
172      *
173      * @throws IllegalArgumentException if the requested state is unsupported.
174      *
175      * @see DeviceStateRequest
176      * @hide
177      */
178     @TestApi
179     @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
requestBaseStateOverride(@onNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback)180     public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
181             @Nullable @CallbackExecutor Executor executor,
182             @Nullable DeviceStateRequest.Callback callback) {
183         mGlobal.requestBaseStateOverride(request, executor, callback);
184     }
185 
186     /**
187      * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
188      * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
189      * <p>
190      * This method is noop if there is no base state request currently active.
191      *
192      * @hide
193      */
194     @TestApi
195     @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE)
cancelBaseStateOverride()196     public void cancelBaseStateOverride() {
197         mGlobal.cancelBaseStateOverride();
198     }
199 
200     /**
201      * Registers a callback to receive notifications about changes in device state.
202      *
203      * @param executor the executor to process notifications.
204      * @param callback the callback to register.
205      *
206      * @see DeviceStateCallback
207      */
registerCallback(@onNull @allbackExecutor Executor executor, @NonNull DeviceStateCallback callback)208     public void registerCallback(@NonNull @CallbackExecutor Executor executor,
209             @NonNull DeviceStateCallback callback) {
210         mGlobal.registerDeviceStateCallback(callback, executor);
211     }
212 
213     /**
214      * Unregisters a callback previously registered with
215      * {@link #registerCallback(Executor, DeviceStateCallback)}.
216      */
unregisterCallback(@onNull DeviceStateCallback callback)217     public void unregisterCallback(@NonNull DeviceStateCallback callback) {
218         mGlobal.unregisterDeviceStateCallback(callback);
219     }
220 
221     /** Callback to receive notifications about changes in device state. */
222     public interface DeviceStateCallback {
223         /**
224          * Called in response to a change in the states supported by the device.
225          * <p>
226          * Guaranteed to be called once on registration of the callback with the initial value and
227          * then on every subsequent change in the supported states.
228          *
229          * The supported device states may change due to certain states becoming unavailable
230          * due to device configuration or device conditions such as if the device is too hot or
231          * external monitors have been connected.
232          *
233          * @param supportedStates the new supported states.
234          *
235          * @see DeviceStateManager#getSupportedDeviceStates()
236          */
onSupportedStatesChanged(@onNull List<DeviceState> supportedStates)237         default void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {}
238 
239         /**
240          * Called in response to device state changes.
241          * <p>
242          * Guaranteed to be called once on registration of the callback with the initial value and
243          * then on every subsequent change in device state.
244          *
245          * @param state the new device state.
246          */
onDeviceStateChanged(@onNull DeviceState state)247         void onDeviceStateChanged(@NonNull DeviceState state);
248     }
249 
250     /**
251      * Listens to changes in device state and reports the state as folded if the device state
252      * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState}
253      * resource.
254      * @hide
255      */
256     public static class FoldStateListener implements DeviceStateCallback {
257         private final int[] mFoldedDeviceStates;
258         private final Consumer<Boolean> mDelegate;
259         private final android.hardware.devicestate.feature.flags.FeatureFlags mFeatureFlags;
260 
261         @Nullable
262         private Boolean lastResult;
263 
FoldStateListener(Context context)264         public FoldStateListener(Context context) {
265             this(context, folded -> {});
266         }
267 
FoldStateListener(Context context, Consumer<Boolean> listener)268         public FoldStateListener(Context context, Consumer<Boolean> listener) {
269             mFoldedDeviceStates = context.getResources().getIntArray(
270                     com.android.internal.R.array.config_foldedDeviceStates);
271             mDelegate = listener;
272             mFeatureFlags = new android.hardware.devicestate.feature.flags.FeatureFlagsImpl();
273         }
274 
275         @Override
onDeviceStateChanged(@onNull DeviceState deviceState)276         public final void onDeviceStateChanged(@NonNull DeviceState deviceState) {
277             final boolean folded;
278             if (mFeatureFlags.deviceStatePropertyApi()) {
279                 // TODO(b/325124054): Update when system server refactor is completed
280                 folded = deviceState.hasProperty(
281                         DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
282                         || ArrayUtils.contains(mFoldedDeviceStates, deviceState.getIdentifier());
283             } else {
284                 folded = ArrayUtils.contains(mFoldedDeviceStates, deviceState.getIdentifier());
285             }
286 
287             if (lastResult == null || !lastResult.equals(folded)) {
288                 lastResult = folded;
289                 mDelegate.accept(folded);
290             }
291         }
292 
293         @Nullable
getFolded()294         public Boolean getFolded() {
295             return lastResult;
296         }
297     }
298 }
299