1 /* 2 * Copyright (C) 2021 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.companion.virtual; 18 19 import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SuppressLint; 24 import android.app.ActivityOptions; 25 import android.companion.AssociationInfo; 26 import android.companion.CompanionDeviceManager; 27 import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; 28 import android.companion.virtual.IVirtualDevice; 29 import android.companion.virtual.IVirtualDeviceActivityListener; 30 import android.companion.virtual.IVirtualDeviceManager; 31 import android.companion.virtual.VirtualDeviceManager; 32 import android.companion.virtual.VirtualDeviceParams; 33 import android.content.Context; 34 import android.hardware.display.DisplayManagerInternal; 35 import android.hardware.display.IVirtualDisplayCallback; 36 import android.hardware.display.VirtualDisplayConfig; 37 import android.os.Binder; 38 import android.os.Handler; 39 import android.os.IBinder; 40 import android.os.Looper; 41 import android.os.Parcel; 42 import android.os.RemoteException; 43 import android.os.UserHandle; 44 import android.util.ArraySet; 45 import android.util.ExceptionUtils; 46 import android.util.Slog; 47 import android.util.SparseArray; 48 import android.widget.Toast; 49 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.internal.util.DumpUtils; 53 import com.android.server.SystemService; 54 import com.android.server.companion.virtual.VirtualDeviceImpl.PendingTrampoline; 55 import com.android.server.wm.ActivityInterceptorCallback; 56 import com.android.server.wm.ActivityTaskManagerInternal; 57 58 import java.io.FileDescriptor; 59 import java.io.PrintWriter; 60 import java.util.ArrayList; 61 import java.util.List; 62 import java.util.concurrent.ConcurrentHashMap; 63 64 65 @SuppressLint("LongLogTag") 66 public class VirtualDeviceManagerService extends SystemService { 67 68 private static final boolean DEBUG = false; 69 private static final String TAG = "VirtualDeviceManagerService"; 70 71 private final Object mVirtualDeviceManagerLock = new Object(); 72 private final VirtualDeviceManagerImpl mImpl; 73 private final VirtualDeviceManagerInternal mLocalService; 74 private final Handler mHandler = new Handler(Looper.getMainLooper()); 75 private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); 76 /** 77 * Mapping from user IDs to CameraAccessControllers. 78 */ 79 @GuardedBy("mVirtualDeviceManagerLock") 80 private final SparseArray<CameraAccessController> mCameraAccessControllers = 81 new SparseArray<>(); 82 83 /** 84 * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for 85 * each CDM associated device. 86 */ 87 @GuardedBy("mVirtualDeviceManagerLock") 88 private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>(); 89 90 /** 91 * Mapping from CDM association IDs to app UIDs running on the corresponding virtual device. 92 */ 93 @GuardedBy("mVirtualDeviceManagerLock") 94 private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>(); 95 96 /** 97 * Mapping from user ID to CDM associations. The associations come from 98 * {@link CompanionDeviceManager#getAllAssociations()}, which contains associations across all 99 * packages. 100 */ 101 private final ConcurrentHashMap<Integer, List<AssociationInfo>> mAllAssociations = 102 new ConcurrentHashMap<>(); 103 104 /** 105 * Mapping from user ID to its change listener. The listeners are added when the user is 106 * started and removed when the user stops. 107 */ 108 private final SparseArray<OnAssociationsChangedListener> mOnAssociationsChangedListeners = 109 new SparseArray<>(); 110 VirtualDeviceManagerService(Context context)111 public VirtualDeviceManagerService(Context context) { 112 super(context); 113 mImpl = new VirtualDeviceManagerImpl(); 114 mLocalService = new LocalService(); 115 } 116 117 private final ActivityInterceptorCallback mActivityInterceptorCallback = 118 new ActivityInterceptorCallback() { 119 120 @Nullable 121 @Override 122 public ActivityInterceptResult intercept(ActivityInterceptorInfo info) { 123 if (info.callingPackage == null) { 124 return null; 125 } 126 PendingTrampoline pt = mPendingTrampolines.remove(info.callingPackage); 127 if (pt == null) { 128 return null; 129 } 130 pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null); 131 ActivityOptions options = info.checkedOptions; 132 if (options == null) { 133 options = ActivityOptions.makeBasic(); 134 } 135 return new ActivityInterceptResult( 136 info.intent, options.setLaunchDisplayId(pt.mDisplayId)); 137 } 138 }; 139 140 @Override onStart()141 public void onStart() { 142 publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); 143 publishLocalService(VirtualDeviceManagerInternal.class, mLocalService); 144 ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService( 145 ActivityTaskManagerInternal.class); 146 activityTaskManagerInternal.registerActivityStartInterceptor( 147 VIRTUAL_DEVICE_SERVICE_ORDERED_ID, 148 mActivityInterceptorCallback); 149 } 150 151 @GuardedBy("mVirtualDeviceManagerLock") isValidVirtualDeviceLocked(IVirtualDevice virtualDevice)152 private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) { 153 try { 154 return mVirtualDevices.contains(virtualDevice.getAssociationId()); 155 } catch (RemoteException e) { 156 throw e.rethrowFromSystemServer(); 157 } 158 } 159 160 @Override onUserStarting(@onNull TargetUser user)161 public void onUserStarting(@NonNull TargetUser user) { 162 super.onUserStarting(user); 163 Context userContext = getContext().createContextAsUser(user.getUserHandle(), 0); 164 synchronized (mVirtualDeviceManagerLock) { 165 final CompanionDeviceManager cdm = 166 userContext.getSystemService(CompanionDeviceManager.class); 167 final int userId = user.getUserIdentifier(); 168 mAllAssociations.put(userId, cdm.getAllAssociations()); 169 OnAssociationsChangedListener listener = 170 associations -> mAllAssociations.put(userId, associations); 171 mOnAssociationsChangedListeners.put(userId, listener); 172 cdm.addOnAssociationsChangedListener(Runnable::run, listener); 173 CameraAccessController cameraAccessController = new CameraAccessController( 174 userContext, mLocalService, this::onCameraAccessBlocked); 175 mCameraAccessControllers.put(user.getUserIdentifier(), cameraAccessController); 176 } 177 } 178 179 @Override onUserStopping(@onNull TargetUser user)180 public void onUserStopping(@NonNull TargetUser user) { 181 super.onUserStopping(user); 182 synchronized (mVirtualDeviceManagerLock) { 183 int userId = user.getUserIdentifier(); 184 mAllAssociations.remove(userId); 185 final CompanionDeviceManager cdm = getContext().createContextAsUser( 186 user.getUserHandle(), 0) 187 .getSystemService(CompanionDeviceManager.class); 188 OnAssociationsChangedListener listener = mOnAssociationsChangedListeners.get(userId); 189 if (listener != null) { 190 cdm.removeOnAssociationsChangedListener(listener); 191 mOnAssociationsChangedListeners.remove(userId); 192 } 193 CameraAccessController cameraAccessController = mCameraAccessControllers.get( 194 user.getUserIdentifier()); 195 if (cameraAccessController != null) { 196 cameraAccessController.close(); 197 mCameraAccessControllers.remove(user.getUserIdentifier()); 198 } else { 199 Slog.w(TAG, "Cannot unregister cameraAccessController for user " + user); 200 } 201 } 202 } 203 onCameraAccessBlocked(int appUid)204 void onCameraAccessBlocked(int appUid) { 205 synchronized (mVirtualDeviceManagerLock) { 206 int size = mVirtualDevices.size(); 207 for (int i = 0; i < size; i++) { 208 CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName(); 209 mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid, 210 getContext().getString( 211 com.android.internal.R.string.vdm_camera_access_denied, 212 deviceName), 213 Toast.LENGTH_LONG, Looper.myLooper()); 214 } 215 } 216 } 217 218 @VisibleForTesting getLocalServiceInstance()219 VirtualDeviceManagerInternal getLocalServiceInstance() { 220 return mLocalService; 221 } 222 223 @VisibleForTesting notifyRunningAppsChanged(int associationId, ArraySet<Integer> uids)224 void notifyRunningAppsChanged(int associationId, ArraySet<Integer> uids) { 225 synchronized (mVirtualDeviceManagerLock) { 226 mAppsOnVirtualDevices.put(associationId, uids); 227 } 228 mLocalService.onAppsOnVirtualDeviceChanged(); 229 } 230 231 class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements 232 VirtualDeviceImpl.PendingTrampolineCallback { 233 234 @Override // Binder call createVirtualDevice( IBinder token, String packageName, int associationId, @NonNull VirtualDeviceParams params, @NonNull IVirtualDeviceActivityListener activityListener)235 public IVirtualDevice createVirtualDevice( 236 IBinder token, 237 String packageName, 238 int associationId, 239 @NonNull VirtualDeviceParams params, 240 @NonNull IVirtualDeviceActivityListener activityListener) { 241 getContext().enforceCallingOrSelfPermission( 242 android.Manifest.permission.CREATE_VIRTUAL_DEVICE, 243 "createVirtualDevice"); 244 final int callingUid = getCallingUid(); 245 if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) { 246 throw new SecurityException( 247 "Package name " + packageName + " does not belong to calling uid " 248 + callingUid); 249 } 250 AssociationInfo associationInfo = getAssociationInfo(packageName, associationId); 251 if (associationInfo == null) { 252 throw new IllegalArgumentException("No association with ID " + associationId); 253 } 254 synchronized (mVirtualDeviceManagerLock) { 255 if (mVirtualDevices.contains(associationId)) { 256 throw new IllegalStateException( 257 "Virtual device for association ID " + associationId 258 + " already exists"); 259 } 260 final int userId = UserHandle.getUserId(callingUid); 261 final CameraAccessController cameraAccessController = 262 mCameraAccessControllers.get(userId); 263 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), 264 associationInfo, token, callingUid, 265 new VirtualDeviceImpl.OnDeviceCloseListener() { 266 @Override 267 public void onClose(int associationId) { 268 synchronized (mVirtualDeviceManagerLock) { 269 mVirtualDevices.remove(associationId); 270 mAppsOnVirtualDevices.remove(associationId); 271 if (cameraAccessController != null) { 272 cameraAccessController.stopObservingIfNeeded(); 273 } else { 274 Slog.w(TAG, "cameraAccessController not found for user " 275 + userId); 276 } 277 } 278 } 279 }, 280 this, activityListener, 281 runningUids -> { 282 cameraAccessController.blockCameraAccessIfNeeded(runningUids); 283 notifyRunningAppsChanged(associationInfo.getId(), runningUids); 284 }, 285 params); 286 if (cameraAccessController != null) { 287 cameraAccessController.startObservingIfNeeded(); 288 } else { 289 Slog.w(TAG, "cameraAccessController not found for user " + userId); 290 } 291 mVirtualDevices.put(associationInfo.getId(), virtualDevice); 292 return virtualDevice; 293 } 294 } 295 296 @Override // Binder call createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, IVirtualDisplayCallback callback, IVirtualDevice virtualDevice, String packageName)297 public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, 298 IVirtualDisplayCallback callback, IVirtualDevice virtualDevice, String packageName) 299 throws RemoteException { 300 final int callingUid = getCallingUid(); 301 if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) { 302 throw new SecurityException( 303 "Package name " + packageName + " does not belong to calling uid " 304 + callingUid); 305 } 306 VirtualDeviceImpl virtualDeviceImpl; 307 synchronized (mVirtualDeviceManagerLock) { 308 virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getAssociationId()); 309 if (virtualDeviceImpl == null) { 310 throw new SecurityException("Invalid VirtualDevice"); 311 } 312 } 313 if (virtualDeviceImpl.getOwnerUid() != callingUid) { 314 throw new SecurityException( 315 "uid " + callingUid 316 + " is not the owner of the supplied VirtualDevice"); 317 } 318 GenericWindowPolicyController gwpc; 319 final long token = Binder.clearCallingIdentity(); 320 try { 321 gwpc = virtualDeviceImpl.createWindowPolicyController(); 322 } finally { 323 Binder.restoreCallingIdentity(token); 324 } 325 326 DisplayManagerInternal displayManager = getLocalService( 327 DisplayManagerInternal.class); 328 int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback, 329 virtualDevice, gwpc, packageName); 330 331 final long tokenTwo = Binder.clearCallingIdentity(); 332 try { 333 virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId); 334 } finally { 335 Binder.restoreCallingIdentity(tokenTwo); 336 } 337 mLocalService.onVirtualDisplayCreated(displayId); 338 return displayId; 339 } 340 341 @Nullable getAssociationInfo(String packageName, int associationId)342 private AssociationInfo getAssociationInfo(String packageName, int associationId) { 343 final int callingUserId = getCallingUserHandle().getIdentifier(); 344 final List<AssociationInfo> associations = 345 mAllAssociations.get(callingUserId); 346 if (associations != null) { 347 final int associationSize = associations.size(); 348 for (int i = 0; i < associationSize; i++) { 349 AssociationInfo associationInfo = associations.get(i); 350 if (associationInfo.belongsToPackage(callingUserId, packageName) 351 && associationId == associationInfo.getId()) { 352 return associationInfo; 353 } 354 } 355 } else { 356 Slog.w(TAG, "No associations for user " + callingUserId); 357 } 358 return null; 359 } 360 361 @Override onTransact(int code, Parcel data, Parcel reply, int flags)362 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 363 throws RemoteException { 364 try { 365 return super.onTransact(code, data, reply, flags); 366 } catch (Throwable e) { 367 Slog.e(TAG, "Error during IPC", e); 368 throw ExceptionUtils.propagate(e, RemoteException.class); 369 } 370 } 371 372 @Override dump(@onNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args)373 public void dump(@NonNull FileDescriptor fd, 374 @NonNull PrintWriter fout, 375 @Nullable String[] args) { 376 if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, fout)) { 377 return; 378 } 379 fout.println("Created virtual devices: "); 380 synchronized (mVirtualDeviceManagerLock) { 381 for (int i = 0; i < mVirtualDevices.size(); i++) { 382 mVirtualDevices.valueAt(i).dump(fd, fout, args); 383 } 384 } 385 } 386 387 @Override startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline)388 public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) { 389 PendingTrampoline existing = mPendingTrampolines.put( 390 pendingTrampoline.mPendingIntent.getCreatorPackage(), 391 pendingTrampoline); 392 if (existing != null) { 393 existing.mResultReceiver.send( 394 VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null); 395 } 396 } 397 398 @Override stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline)399 public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) { 400 mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage()); 401 } 402 } 403 404 private final class LocalService extends VirtualDeviceManagerInternal { 405 @GuardedBy("mVirtualDeviceManagerLock") 406 private final ArrayList<VirtualDisplayListener> 407 mVirtualDisplayListeners = new ArrayList<>(); 408 @GuardedBy("mVirtualDeviceManagerLock") 409 private final ArrayList<AppsOnVirtualDeviceListener> 410 mAppsOnVirtualDeviceListeners = new ArrayList<>(); 411 @GuardedBy("mVirtualDeviceManagerLock") 412 private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>(); 413 414 @Override isValidVirtualDevice(IVirtualDevice virtualDevice)415 public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) { 416 synchronized (mVirtualDeviceManagerLock) { 417 return isValidVirtualDeviceLocked(virtualDevice); 418 } 419 } 420 421 @Override onVirtualDisplayCreated(int displayId)422 public void onVirtualDisplayCreated(int displayId) { 423 final VirtualDisplayListener[] listeners; 424 synchronized (mVirtualDeviceManagerLock) { 425 listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); 426 } 427 mHandler.post(() -> { 428 for (VirtualDisplayListener listener : listeners) { 429 listener.onVirtualDisplayCreated(displayId); 430 } 431 }); 432 } 433 434 @Override onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId)435 public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) { 436 final VirtualDisplayListener[] listeners; 437 synchronized (mVirtualDeviceManagerLock) { 438 ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId); 439 listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); 440 } 441 mHandler.post(() -> { 442 for (VirtualDisplayListener listener : listeners) { 443 listener.onVirtualDisplayRemoved(displayId); 444 } 445 }); 446 } 447 448 @Override onAppsOnVirtualDeviceChanged()449 public void onAppsOnVirtualDeviceChanged() { 450 ArraySet<Integer> latestRunningUids = new ArraySet<>(); 451 final AppsOnVirtualDeviceListener[] listeners; 452 synchronized (mVirtualDeviceManagerLock) { 453 int size = mAppsOnVirtualDevices.size(); 454 for (int i = 0; i < size; i++) { 455 latestRunningUids.addAll(mAppsOnVirtualDevices.valueAt(i)); 456 } 457 if (!mAllUidsOnVirtualDevice.equals(latestRunningUids)) { 458 mAllUidsOnVirtualDevice.clear(); 459 mAllUidsOnVirtualDevice.addAll(latestRunningUids); 460 listeners = 461 mAppsOnVirtualDeviceListeners.toArray( 462 new AppsOnVirtualDeviceListener[0]); 463 } else { 464 listeners = null; 465 } 466 } 467 if (listeners != null) { 468 mHandler.post(() -> { 469 for (AppsOnVirtualDeviceListener listener : listeners) { 470 listener.onAppsOnAnyVirtualDeviceChanged(latestRunningUids); 471 } 472 }); 473 } 474 } 475 476 @Override getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice)477 public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) { 478 return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags(); 479 } 480 481 @Override isAppOwnerOfAnyVirtualDevice(int uid)482 public boolean isAppOwnerOfAnyVirtualDevice(int uid) { 483 synchronized (mVirtualDeviceManagerLock) { 484 int size = mVirtualDevices.size(); 485 for (int i = 0; i < size; i++) { 486 if (mVirtualDevices.valueAt(i).getOwnerUid() == uid) { 487 return true; 488 } 489 } 490 return false; 491 } 492 } 493 494 @Override isAppRunningOnAnyVirtualDevice(int uid)495 public boolean isAppRunningOnAnyVirtualDevice(int uid) { 496 synchronized (mVirtualDeviceManagerLock) { 497 int size = mVirtualDevices.size(); 498 for (int i = 0; i < size; i++) { 499 if (mVirtualDevices.valueAt(i).isAppRunningOnVirtualDevice(uid)) { 500 return true; 501 } 502 } 503 } 504 return false; 505 } 506 507 @Override isDisplayOwnedByAnyVirtualDevice(int displayId)508 public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) { 509 synchronized (mVirtualDeviceManagerLock) { 510 int size = mVirtualDevices.size(); 511 for (int i = 0; i < size; i++) { 512 if (mVirtualDevices.valueAt(i).isDisplayOwnedByVirtualDevice(displayId)) { 513 return true; 514 } 515 } 516 } 517 return false; 518 } 519 520 @Override registerVirtualDisplayListener( @onNull VirtualDisplayListener listener)521 public void registerVirtualDisplayListener( 522 @NonNull VirtualDisplayListener listener) { 523 synchronized (mVirtualDeviceManagerLock) { 524 mVirtualDisplayListeners.add(listener); 525 } 526 } 527 528 @Override unregisterVirtualDisplayListener( @onNull VirtualDisplayListener listener)529 public void unregisterVirtualDisplayListener( 530 @NonNull VirtualDisplayListener listener) { 531 synchronized (mVirtualDeviceManagerLock) { 532 mVirtualDisplayListeners.remove(listener); 533 } 534 } 535 536 @Override registerAppsOnVirtualDeviceListener( @onNull AppsOnVirtualDeviceListener listener)537 public void registerAppsOnVirtualDeviceListener( 538 @NonNull AppsOnVirtualDeviceListener listener) { 539 synchronized (mVirtualDeviceManagerLock) { 540 mAppsOnVirtualDeviceListeners.add(listener); 541 } 542 } 543 544 @Override unregisterAppsOnVirtualDeviceListener( @onNull AppsOnVirtualDeviceListener listener)545 public void unregisterAppsOnVirtualDeviceListener( 546 @NonNull AppsOnVirtualDeviceListener listener) { 547 synchronized (mVirtualDeviceManagerLock) { 548 mAppsOnVirtualDeviceListeners.remove(listener); 549 } 550 } 551 } 552 553 private static final class PendingTrampolineMap { 554 /** 555 * The maximum duration, in milliseconds, to wait for a trampoline activity launch after 556 * invoking a pending intent. 557 */ 558 private static final int TRAMPOLINE_WAIT_MS = 5000; 559 560 private final ConcurrentHashMap<String, PendingTrampoline> mMap = new ConcurrentHashMap<>(); 561 private final Handler mHandler; 562 PendingTrampolineMap(Handler handler)563 PendingTrampolineMap(Handler handler) { 564 mHandler = handler; 565 } 566 put( @onNull String packageName, @NonNull PendingTrampoline pendingTrampoline)567 PendingTrampoline put( 568 @NonNull String packageName, @NonNull PendingTrampoline pendingTrampoline) { 569 PendingTrampoline existing = mMap.put(packageName, pendingTrampoline); 570 mHandler.removeCallbacksAndMessages(existing); 571 mHandler.postDelayed( 572 () -> { 573 final String creatorPackage = 574 pendingTrampoline.mPendingIntent.getCreatorPackage(); 575 if (creatorPackage != null) { 576 remove(creatorPackage); 577 } 578 }, 579 pendingTrampoline, 580 TRAMPOLINE_WAIT_MS); 581 return existing; 582 } 583 remove(@onNull String packageName)584 PendingTrampoline remove(@NonNull String packageName) { 585 PendingTrampoline pendingTrampoline = mMap.remove(packageName); 586 mHandler.removeCallbacksAndMessages(pendingTrampoline); 587 return pendingTrampoline; 588 } 589 } 590 } 591