• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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