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