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