1 /*
2  * Copyright (C) 2022 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 com.android.server.devicelock;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.devicelock.ParcelableException;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.IBinder;
30 import android.os.OutcomeReceiver;
31 import android.os.RemoteCallback;
32 import android.os.UserHandle;
33 import android.text.TextUtils;
34 import android.util.ArraySet;
35 import android.util.Slog;
36 
37 import com.android.devicelockcontroller.IDeviceLockControllerService;
38 import com.android.internal.annotations.GuardedBy;
39 
40 import java.util.concurrent.Callable;
41 import java.util.concurrent.ExecutorService;
42 import java.util.concurrent.Executors;
43 import java.util.concurrent.TimeoutException;
44 
45 /**
46  * This class is used to establish a connection (bind) to the Device Lock Controller APK.
47  */
48 final class DeviceLockControllerConnectorImpl implements DeviceLockControllerConnector {
49     private final Object mLock = new Object();
50 
51     private static final String TAG = "DeviceLockControllerConnectorImpl";
52 
53     @GuardedBy("mLock")
54     private IDeviceLockControllerService mDeviceLockControllerService;
55 
56     @GuardedBy("mLock")
57     private ServiceConnection mServiceConnection;
58 
59     private final Context mContext;
60     private final ComponentName mComponentName;
61     private final UserHandle mUserHandle;
62     private final Handler mHandler;
63 
64     private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
65 
66     private static final long INACTIVITY_TIMEOUT_MILLIS = 1_000 * 60 * 1; // One minute.
67     private static final long API_CALL_TIMEOUT_MILLIS = 1_000 * 10;       // Ten seconds.
68 
69     // The following hash set is used for API timeout detection. We do oneway calls into the
70     // device lock controller service, and the service is supposed to reply with another one way
71     // call. Once we call into the device lock controller service, we add the callback to this
72     // hash map and remove it once the remote invocation from the controller is received by
73     // the system service or a timeout occurred. In this way, we guarantee that the callback
74     // will be always invoked (and it's only invoked once).
75     @GuardedBy("mPendingCallbacks")
76     private final ArraySet<OutcomeReceiver> mPendingCallbacks = new ArraySet<>();
77 
78     private final Runnable mUnbindDeviceLockControllerService = () -> {
79         Slog.i(TAG, "Unbinding DeviceLockControllerService");
80         unbind();
81     };
82 
callControllerApi(Callable<Void> body, OutcomeReceiver<Result, Exception> callback)83     private <Result> void callControllerApi(Callable<Void> body,
84             OutcomeReceiver<Result, Exception> callback) {
85         Runnable r = () -> {
86             Exception exception = null;
87 
88             mHandler.removeCallbacks(mUnbindDeviceLockControllerService);
89             mHandler.postDelayed(mUnbindDeviceLockControllerService, INACTIVITY_TIMEOUT_MILLIS);
90 
91             synchronized (mLock) {
92                 // First, bind if not already bound.
93                 if (bindLocked()) {
94                     while (mDeviceLockControllerService == null) {
95                         try {
96                             mLock.wait();
97                         } catch (InterruptedException e) {
98                             // Nothing to do, wait again if mService is still null.
99                         }
100                     }
101 
102                     try {
103                         synchronized (mPendingCallbacks) {
104                             mPendingCallbacks.add(callback);
105                         }
106                         body.call();
107                         // Start timeout for this call.
108                         mHandler.postDelayed(() -> {
109                             boolean removed;
110                             synchronized (mPendingCallbacks) {
111                                 removed = mPendingCallbacks.remove(callback);
112                             }
113                             if (removed) {
114                                 // We hit a timeout, execute the callback.
115                                 mHandler.post(() -> callback.onError(new TimeoutException()));
116                             }
117                         }, API_CALL_TIMEOUT_MILLIS);
118                     } catch (Exception e) {
119                         synchronized (mPendingCallbacks) {
120                             mPendingCallbacks.remove(callback);
121                         }
122                         exception = e;
123                     }
124                 } else {
125                     exception = new Exception("Failed to bind to service");
126                 }
127             }
128 
129             if (exception != null) {
130                 final Exception finalException = exception;
131                 mHandler.post(() -> callback.onError(finalException));
132                 return;
133             }
134         };
135 
136         mExecutorService.execute(r);
137     }
138 
hasApiCallTimedOut(OutcomeReceiver callback)139     private boolean hasApiCallTimedOut(OutcomeReceiver callback) {
140         boolean removed;
141         synchronized (mPendingCallbacks) {
142             removed = mPendingCallbacks.remove(callback);
143         }
144         // if this callback was already been removed by the timeout and somehow this callback
145         // arrived late. We already replied with a timeout error, ignore the result.
146 
147         return !removed;
148     }
149 
checkTimeout(OutcomeReceiver callback, RemoteCallback.OnResultListener listener)150     private RemoteCallback.OnResultListener checkTimeout(OutcomeReceiver callback,
151             RemoteCallback.OnResultListener listener) {
152         return (@Nullable Bundle bundle) -> {
153             if (hasApiCallTimedOut(callback)) {
154                 return;
155             }
156             listener.onResult(bundle);
157         };
158     }
159 
160     private class DeviceLockControllerServiceConnection implements ServiceConnection {
161         @Override
162         public void onServiceConnected(ComponentName name, IBinder service) {
163             synchronized (mLock) {
164                 if (mServiceConnection == null) {
165                     Slog.w(TAG, "Connected: " + mComponentName.flattenToShortString()
166                             + " but not bound, ignore.");
167                     return;
168                 }
169 
170                 Slog.i(TAG, "Connected to " + mComponentName.flattenToShortString());
171 
172                 mDeviceLockControllerService =
173                         IDeviceLockControllerService.Stub.asInterface(service);
174 
175                 mLock.notifyAll();
176             }
177         }
178 
179         @Override
180         public void onServiceDisconnected(ComponentName name) {
181             Slog.i(TAG, "Disconnected from " + mComponentName.flattenToShortString());
182             // Service has crashed or been killed. The binding is still valid, however
183             // we unbind here so we can bind again an restart the service when needed.
184             // Otherwise, Activity Manager would restart it after some back-off, and trying
185             // to use the service in this timeframe would result in a DeadObjectException.
186             unbind();
187         }
188 
189         @Override
190         public void onBindingDied(ComponentName name) {
191             // Activity Manager gave up.
192             synchronized (mLock) {
193                 if (mServiceConnection == null) {
194                     // Callback came in late
195                     Slog.w(TAG, "Binding died: " + mComponentName.flattenToShortString()
196                             + " but not bound, ignore.");
197                     return;
198                 }
199 
200                 Slog.w(TAG, "Binding died " + mComponentName.flattenToShortString());
201 
202                 // We just unbind here; any API calls would cause the binding to be recreated
203                 // when needed.
204                 unbindLocked();
205             }
206         }
207     }
208 
209     /**
210      * Create a new connector to the Device Lock Controller service.
211      *
212      * @param context       the context for this call.
213      * @param componentName Device Lock Controller service component name.
214      */
215     DeviceLockControllerConnectorImpl(@NonNull Context context,
216             @NonNull ComponentName componentName, @NonNull UserHandle userHandle) {
217         mContext = context;
218         mComponentName = componentName;
219         mUserHandle = userHandle;
220 
221         HandlerThread handlerThread =
222                 new HandlerThread("DeviceLockControllerConnectorHandlerThread");
223         handlerThread.start();
224         mHandler = new Handler(handlerThread.getLooper());
225     }
226 
227     @GuardedBy("mLock")
228     private boolean bindLocked() {
229         if (mServiceConnection != null) {
230             // Already bound, ignore and return success.
231             return true;
232         }
233 
234         mServiceConnection = new DeviceLockControllerServiceConnection();
235 
236         final Intent service = new Intent().setComponent(mComponentName);
237         final boolean bound = mContext.bindServiceAsUser(service, mServiceConnection,
238                 Context.BIND_AUTO_CREATE, mUserHandle);
239 
240         if (bound) {
241             Slog.i(TAG, "Binding " + mComponentName.flattenToShortString());
242         } else {
243             // As per bindService() documentation, we still need to call unbindService()
244             // if binding fails.
245             mContext.unbindService(mServiceConnection);
246             mServiceConnection = null;
247             Slog.e(TAG, "Binding " + mComponentName.flattenToShortString() + " failed.");
248         }
249 
250         return bound;
251     }
252 
253     @GuardedBy("mLock")
254     private void unbindLocked() {
255         if (mServiceConnection == null) {
256             return;
257         }
258 
259         Slog.i(TAG, "Unbinding " + mComponentName.flattenToShortString());
260 
261         mContext.unbindService(mServiceConnection);
262 
263         mDeviceLockControllerService = null;
264         mServiceConnection = null;
265     }
266 
267     @Override
268     public void unbind() {
269         synchronized (mLock) {
270             unbindLocked();
271         }
272     }
273 
274     /**
275      * Report an exception (if any) using the callback.
276      *
277      * @param callback Callback used to report the exception.
278      * @param bundle Bundle where to look for a parcelable exception.
279      *
280      * @return true if an exception was reported.
281      */
282     private boolean maybeReportException(@NonNull OutcomeReceiver<?, Exception> callback,
283             @NonNull Bundle bundle) {
284         bundle.setClassLoader(this.getClass().getClassLoader());
285         final ParcelableException parcelableException =
286                 bundle.getSerializable(IDeviceLockControllerService.KEY_PARCELABLE_EXCEPTION,
287                         ParcelableException.class);
288         if (parcelableException != null) {
289             mHandler.post(() -> callback.onError(parcelableException));
290             return true;
291         }
292 
293         return false;
294     }
295 
296     @Override
297     public void lockDevice(OutcomeReceiver<Void, Exception> callback) {
298         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
299             if (maybeReportException(callback, result)) {
300                 return;
301             }
302 
303             mHandler.post(() -> callback.onResult(null));
304         }));
305 
306         callControllerApi(new Callable<Void>() {
307             @Override
308             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
309             public Void call() throws Exception {
310                 mDeviceLockControllerService.lockDevice(remoteCallback);
311                 return null;
312             }
313         }, callback);
314     }
315 
316     @Override
317     public void unlockDevice(OutcomeReceiver<Void, Exception> callback) {
318         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
319             if (maybeReportException(callback, result)) {
320                 return;
321             }
322 
323             mHandler.post(() -> callback.onResult(null));
324         }));
325 
326         callControllerApi(new Callable<Void>() {
327             @Override
328             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
329             public Void call() throws Exception {
330                 mDeviceLockControllerService.unlockDevice(remoteCallback);
331                 return null;
332             }
333         }, callback);
334     }
335 
336     @Override
337     public void isDeviceLocked(OutcomeReceiver<Boolean, Exception> callback) {
338         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
339             if (maybeReportException(callback, result)) {
340                 return;
341             }
342 
343             final boolean isLocked =
344                     result.getBoolean(IDeviceLockControllerService.KEY_RESULT);
345             mHandler.post(() -> callback.onResult(isLocked));
346         }));
347 
348         callControllerApi(new Callable<Void>() {
349             @Override
350             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
351             public Void call() throws Exception {
352                 mDeviceLockControllerService.isDeviceLocked(remoteCallback);
353                 return null;
354             }
355         }, callback);
356     }
357 
358     @Override
359     public void getDeviceId(OutcomeReceiver<String, Exception> callback) {
360         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
361             if (maybeReportException(callback, result)) {
362                 return;
363             }
364 
365             final String deviceId =
366                     result.getString(IDeviceLockControllerService.KEY_RESULT);
367             if (TextUtils.isEmpty(deviceId)) { // If the deviceId is null or empty
368                 mHandler.post(() -> callback.onError(new IllegalStateException(
369                         "No registered Device ID found")));
370             } else {
371                 mHandler.post(() -> callback.onResult(deviceId));
372             }
373         }));
374 
375         callControllerApi(new Callable<Void>() {
376             @Override
377             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
378             public Void call() throws Exception {
379                 mDeviceLockControllerService.getDeviceIdentifier(remoteCallback);
380                 return null;
381             }
382         }, callback);
383     }
384 
385     @Override
386     public void clearDeviceRestrictions(OutcomeReceiver<Void, Exception> callback) {
387         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
388             if (maybeReportException(callback, result)) {
389                 return;
390             }
391 
392             mHandler.post(() -> callback.onResult(null));
393         }));
394 
395         callControllerApi(new Callable<Void>() {
396             @Override
397             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
398             public Void call() throws Exception {
399                 mDeviceLockControllerService.clearDeviceRestrictions(remoteCallback);
400                 return null;
401             }
402         }, callback);
403     }
404 
405     @Override
406     public void onUserSwitching(OutcomeReceiver<Void, Exception> callback) {
407         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
408             if (maybeReportException(callback, result)) {
409                 return;
410             }
411 
412             mHandler.post(() -> callback.onResult(null));
413         }));
414 
415         callControllerApi(new Callable<Void>() {
416             @Override
417             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
418             public Void call() throws Exception {
419                 mDeviceLockControllerService.onUserSwitching(remoteCallback);
420                 return null;
421             }
422         }, callback);
423     }
424 
425     @Override
426     public void onUserUnlocked(OutcomeReceiver<Void, Exception> callback) {
427         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
428             if (maybeReportException(callback, result)) {
429                 return;
430             }
431 
432             mHandler.post(() -> callback.onResult(null));
433         }));
434 
435         callControllerApi(new Callable<Void>() {
436             @Override
437             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
438             public Void call() throws Exception {
439                 mDeviceLockControllerService.onUserUnlocked(remoteCallback);
440                 return null;
441             }
442         }, callback);
443     }
444 
445     @Override
446     public void onUserSetupCompleted(OutcomeReceiver<Void, Exception> callback) {
447         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
448             if (maybeReportException(callback, result)) {
449                 return;
450             }
451 
452             mHandler.post(() -> callback.onResult(null));
453         }));
454 
455         callControllerApi(new Callable<Void>() {
456             @Override
457             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
458             public Void call() throws Exception {
459                 mDeviceLockControllerService.onUserSetupCompleted(remoteCallback);
460                 return null;
461             }
462         }, callback);
463     }
464 
465     @Override
466     public void onAppCrashed(boolean isKiosk, OutcomeReceiver<Void, Exception> callback) {
467         RemoteCallback remoteCallback = new RemoteCallback(checkTimeout(callback, result -> {
468             if (maybeReportException(callback, result)) {
469                 return;
470             }
471 
472             mHandler.post(() -> callback.onResult(null));
473         }));
474 
475         callControllerApi(new Callable<Void>() {
476             @Override
477             @SuppressWarnings("GuardedBy") // mLock already held in callControllerApi (error prone).
478             public Void call() throws Exception {
479                 mDeviceLockControllerService.onAppCrashed(isKiosk, remoteCallback);
480                 return null;
481             }
482         }, callback);
483     }
484 }
485