• 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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.content.Context;
23 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
24 import android.os.Binder;
25 import android.os.Build;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.os.ServiceManager;
29 import android.os.Trace;
30 import android.util.ArrayMap;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.annotations.VisibleForTesting.Visibility;
35 import com.android.window.flags.Flags;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.concurrent.Executor;
40 
41 /**
42  * Provides communication with the device state system service on behalf of applications.
43  *
44  * @see DeviceStateManager
45  *
46  * @hide
47  */
48 @VisibleForTesting(visibility = Visibility.PACKAGE)
49 public final class DeviceStateManagerGlobal {
50     @Nullable
51     @GuardedBy("DeviceStateManagerGlobal.class")
52     private static DeviceStateManagerGlobal sInstance;
53     private static final String TAG = "DeviceStateManagerGlobal";
54     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
55 
56     /**
57      * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
58      * connection with the device state service couldn't be established.
59      */
60     @Nullable
getInstance()61     public static DeviceStateManagerGlobal getInstance() {
62         synchronized (DeviceStateManagerGlobal.class) {
63             if (sInstance == null) {
64                 final IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
65                 if (b != null) {
66                     sInstance = new DeviceStateManagerGlobal(IDeviceStateManager
67                             .Stub.asInterface(b));
68                 }
69             }
70             return sInstance;
71         }
72     }
73 
74     private final Object mLock = new Object();
75     @NonNull
76     private final IDeviceStateManager mDeviceStateManager;
77     @Nullable
78     private DeviceStateManagerCallback mCallback;
79 
80     @GuardedBy("mLock")
81     private final ArrayList<DeviceStateCallbackWrapper> mCallbacks = new ArrayList<>();
82     @GuardedBy("mLock")
83     private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>();
84 
85     @Nullable
86     @GuardedBy("mLock")
87     private DeviceStateInfo mLastReceivedInfo;
88 
89     // Constructor should be called while holding the lock.
90     // @GuardedBy("DeviceStateManagerGlobal.class") can't be used on constructors.
91     @VisibleForTesting
DeviceStateManagerGlobal(@onNull IDeviceStateManager deviceStateManager)92     public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
93         mDeviceStateManager = deviceStateManager;
94         registerCallbackLocked();
95     }
96 
97     /**
98      * Returns {@link List} of supported {@link DeviceState}s.
99      *
100      * @see DeviceStateManager#getSupportedDeviceStates()
101      */
102     @NonNull
getSupportedDeviceStates()103     public List<DeviceState> getSupportedDeviceStates() {
104         synchronized (mLock) {
105             final DeviceStateInfo currentInfo;
106             if (mLastReceivedInfo != null) {
107                 // If we have mLastReceivedInfo a callback is registered for this instance and it
108                 // is receiving the most recent info from the server. Use that info here.
109                 currentInfo = mLastReceivedInfo;
110             } else {
111                 // If mLastReceivedInfo is null there is no registered callback so we manually
112                 // fetch the current info.
113                 try {
114                     currentInfo = mDeviceStateManager.getDeviceStateInfo();
115                 } catch (RemoteException ex) {
116                     throw ex.rethrowFromSystemServer();
117                 }
118             }
119 
120             return List.copyOf(currentInfo.supportedStates);
121         }
122     }
123 
124     /**
125      * Submits a {@link DeviceStateRequest request} to modify the device state.
126      *
127      * @see DeviceStateManager#requestState(DeviceStateRequest, Executor,
128      * DeviceStateRequest.Callback)
129      * @see DeviceStateRequest
130      */
131     @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
132             conditional = true)
requestState(@onNull DeviceStateRequest request, @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback)133     public void requestState(@NonNull DeviceStateRequest request,
134             @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
135         final DeviceStateRequestWrapper requestWrapper =
136                 new DeviceStateRequestWrapper(request, callback, executor);
137         synchronized (mLock) {
138             if (findRequestTokenLocked(request) != null) {
139                 // This request has already been submitted.
140                 return;
141             }
142             // Add the request wrapper to the mRequests array before requesting the state as the
143             // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
144             // same process as this instance.
145             final IBinder token = new Binder();
146             mRequests.put(token, requestWrapper);
147 
148             try {
149                 mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
150             } catch (RemoteException ex) {
151                 mRequests.remove(token);
152                 throw ex.rethrowFromSystemServer();
153             }
154         }
155     }
156 
157     /**
158      * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
159      * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
160      *
161      * @see DeviceStateManager#cancelStateRequest
162      */
163     @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
164             conditional = true)
cancelStateRequest()165     public void cancelStateRequest() {
166         synchronized (mLock) {
167             try {
168                 mDeviceStateManager.cancelStateRequest();
169             } catch (RemoteException ex) {
170                 throw ex.rethrowFromSystemServer();
171             }
172         }
173     }
174 
175     /**
176      * Submits a {@link DeviceStateRequest request} to modify the base state of the device.
177      *
178      * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor,
179      * DeviceStateRequest.Callback)
180      * @see DeviceStateRequest
181      */
182     @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
requestBaseStateOverride(@onNull DeviceStateRequest request, @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback)183     public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
184             @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
185         final DeviceStateRequestWrapper requestWrapper =
186                 new DeviceStateRequestWrapper(request, callback, executor);
187         synchronized (mLock) {
188             if (findRequestTokenLocked(request) != null) {
189                 // This request has already been submitted.
190                 return;
191             }
192             // Add the request wrapper to the mRequests array before requesting the state as the
193             // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
194             // same process as this instance.
195             final IBinder token = new Binder();
196             mRequests.put(token, requestWrapper);
197 
198             try {
199                 mDeviceStateManager.requestBaseStateOverride(token, request.getState(),
200                         request.getFlags());
201             } catch (RemoteException ex) {
202                 mRequests.remove(token);
203                 throw ex.rethrowFromSystemServer();
204             }
205         }
206     }
207 
208     /**
209      * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
210      * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
211      *
212      * @see DeviceStateManager#cancelBaseStateOverride
213      */
214     @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
cancelBaseStateOverride()215     public void cancelBaseStateOverride() {
216         synchronized (mLock) {
217             try {
218                 mDeviceStateManager.cancelBaseStateOverride();
219             } catch (RemoteException ex) {
220                 throw ex.rethrowFromSystemServer();
221             }
222         }
223     }
224 
225     /**
226      * Registers a callback to receive notifications about changes in device state.
227      *
228      * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
229      */
230     @VisibleForTesting(visibility = Visibility.PACKAGE)
registerDeviceStateCallback(@onNull DeviceStateCallback callback, @NonNull Executor executor)231     public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
232             @NonNull Executor executor) {
233         synchronized (mLock) {
234             final int index = findCallbackLocked(callback);
235             if (index != -1) {
236                 // This callback is already registered.
237                 return;
238             }
239             // Add the callback wrapper to the mCallbacks array after registering the callback as
240             // the callback could be triggered immediately if the mDeviceStateManager IBinder is in
241             // the same process as this instance.
242             final DeviceStateCallbackWrapper wrapper =
243                     new DeviceStateCallbackWrapper(callback, executor);
244             mCallbacks.add(wrapper);
245 
246             if (mLastReceivedInfo != null) {
247                 wrapper.notifySupportedDeviceStatesChanged(
248                         List.copyOf(mLastReceivedInfo.supportedStates));
249                 wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState);
250             }
251         }
252     }
253 
254     /**
255      * Unregisters a callback previously registered with
256      * {@link #registerDeviceStateCallback(DeviceStateCallback, Executor)}}.
257      *
258      * @see DeviceStateManager#unregisterCallback(DeviceStateCallback)
259      */
260     @VisibleForTesting(visibility = Visibility.PACKAGE)
unregisterDeviceStateCallback(@onNull DeviceStateCallback callback)261     public void unregisterDeviceStateCallback(@NonNull DeviceStateCallback callback) {
262         synchronized (mLock) {
263             final int indexToRemove = findCallbackLocked(callback);
264             if (indexToRemove != -1) {
265                 mCallbacks.remove(indexToRemove);
266             }
267         }
268     }
269 
270     /**
271      * Provides notification to the system server that a device state feature overlay
272      * was dismissed. This should only be called from the {@link android.app.Activity} that
273      * was showing the overlay corresponding to the feature.
274      *
275      * Validation of there being an overlay visible and pending state request is handled on the
276      * system server.
277      */
onStateRequestOverlayDismissed(boolean shouldCancelRequest)278     public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
279         try {
280             mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest);
281         } catch (RemoteException ex) {
282             throw ex.rethrowFromSystemServer();
283         }
284     }
285 
286     @GuardedBy("DeviceStateManagerGlobal.class")
registerCallbackLocked()287     private void registerCallbackLocked() {
288         mCallback = new DeviceStateManagerCallback();
289         try {
290             if (Flags.wlinfoOncreate()) {
291                 synchronized (mLock) {
292                     mLastReceivedInfo = mDeviceStateManager.registerCallback(mCallback);
293                 }
294             } else {
295                 mDeviceStateManager.registerCallback(mCallback);
296             }
297         } catch (RemoteException ex) {
298             mCallback = null;
299             throw ex.rethrowFromSystemServer();
300         }
301     }
302 
findCallbackLocked(DeviceStateCallback callback)303     private int findCallbackLocked(DeviceStateCallback callback) {
304         for (int i = 0; i < mCallbacks.size(); i++) {
305             if (mCallbacks.get(i).mDeviceStateCallback.equals(callback)) {
306                 return i;
307             }
308         }
309         return -1;
310     }
311 
312     @Nullable
313     @GuardedBy("mLock")
findRequestTokenLocked(@onNull DeviceStateRequest request)314     private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
315         for (int i = 0; i < mRequests.size(); i++) {
316             if (mRequests.valueAt(i).mRequest.equals(request)) {
317                 return mRequests.keyAt(i);
318             }
319         }
320         return null;
321     }
322 
323     /** Handles a call from the server that the device state info has changed. */
handleDeviceStateInfoChanged(@onNull DeviceStateInfo info)324     private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
325         final ArrayList<DeviceStateCallbackWrapper> callbacks;
326         final DeviceStateInfo oldInfo;
327         synchronized (mLock) {
328             oldInfo = mLastReceivedInfo;
329             mLastReceivedInfo = info;
330             callbacks = new ArrayList<>(mCallbacks);
331         }
332 
333         final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
334         if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
335             for (int i = 0; i < callbacks.size(); i++) {
336                 callbacks.get(i).notifySupportedDeviceStatesChanged(
337                         List.copyOf(info.supportedStates));
338             }
339         }
340         if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
341             for (int i = 0; i < callbacks.size(); i++) {
342                 callbacks.get(i).notifyDeviceStateChanged(info.currentState);
343             }
344         }
345     }
346 
347     /**
348      * Handles a call from the server that a request for the supplied {@code token} has become
349      * active.
350      */
handleRequestActive(@onNull IBinder token)351     private void handleRequestActive(@NonNull IBinder token) {
352         final DeviceStateRequestWrapper request;
353         synchronized (mLock) {
354             request = mRequests.get(token);
355         }
356         if (request != null) {
357             request.notifyRequestActive();
358         }
359     }
360 
361     /**
362      * Handles a call from the server that a request for the supplied {@code token} has become
363      * canceled.
364      */
handleRequestCanceled(@onNull IBinder token)365     private void handleRequestCanceled(@NonNull IBinder token) {
366         final DeviceStateRequestWrapper request;
367         synchronized (mLock) {
368             request = mRequests.remove(token);
369         }
370         if (request != null) {
371             request.notifyRequestCanceled();
372         }
373     }
374 
375     private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
376         @Override
onDeviceStateInfoChanged(@onNull DeviceStateInfo info)377         public void onDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
378             handleDeviceStateInfoChanged(info);
379         }
380 
381         @Override
onRequestActive(@onNull IBinder token)382         public void onRequestActive(@NonNull IBinder token) {
383             handleRequestActive(token);
384         }
385 
386         @Override
onRequestCanceled(@onNull IBinder token)387         public void onRequestCanceled(@NonNull IBinder token) {
388             handleRequestCanceled(token);
389         }
390     }
391 
392     private static final class DeviceStateCallbackWrapper {
393         @NonNull
394         private final DeviceStateCallback mDeviceStateCallback;
395         @NonNull
396         private final Executor mExecutor;
397 
DeviceStateCallbackWrapper(@onNull DeviceStateCallback callback, @NonNull Executor executor)398         DeviceStateCallbackWrapper(@NonNull DeviceStateCallback callback,
399                 @NonNull Executor executor) {
400             mDeviceStateCallback = callback;
401             mExecutor = executor;
402         }
403 
notifySupportedDeviceStatesChanged( @onNull List<DeviceState> newSupportedDeviceStates)404         void notifySupportedDeviceStatesChanged(
405                 @NonNull List<DeviceState> newSupportedDeviceStates) {
406             mExecutor.execute(() ->
407                     mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates));
408         }
409 
notifyDeviceStateChanged(DeviceState newDeviceState)410         void notifyDeviceStateChanged(DeviceState newDeviceState) {
411             execute("notifyDeviceStateChanged",
412                     () -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState));
413         }
414 
execute(@onNull String traceName, @NonNull Runnable r)415         private void execute(@NonNull String traceName, @NonNull Runnable r) {
416             mExecutor.execute(() -> {
417                 if (DEBUG) {
418                     Trace.beginSection(
419                             mDeviceStateCallback.getClass().getSimpleName() + "#" + traceName);
420                 }
421                 try {
422                     r.run();
423                 } finally {
424                     if (DEBUG) {
425                         Trace.endSection();
426                     }
427                 }
428             });
429         }
430     }
431 
432     private static final class DeviceStateRequestWrapper {
433         @NonNull
434         private final DeviceStateRequest mRequest;
435         @Nullable
436         private final DeviceStateRequest.Callback mCallback;
437         @Nullable
438         private final Executor mExecutor;
439 
DeviceStateRequestWrapper(@onNull DeviceStateRequest request, @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor)440         DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
441                 @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
442             validateRequestWrapperParameters(callback, executor);
443 
444             mRequest = request;
445             mCallback = callback;
446             mExecutor = executor;
447         }
448 
notifyRequestActive()449         void notifyRequestActive() {
450             if (mCallback == null) {
451                 return;
452             }
453 
454             mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
455         }
456 
notifyRequestCanceled()457         void notifyRequestCanceled() {
458             if (mCallback == null) {
459                 return;
460             }
461 
462             mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest));
463         }
464 
validateRequestWrapperParameters( @ullable DeviceStateRequest.Callback callback, @Nullable Executor executor)465         private void validateRequestWrapperParameters(
466                 @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
467             if (callback == null && executor != null) {
468                 throw new IllegalArgumentException("Callback must be supplied with executor.");
469             } else if (executor == null && callback != null) {
470                 throw new IllegalArgumentException("Executor must be supplied with callback.");
471             }
472         }
473     }
474 }
475