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