/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.am; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.app.Activity; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.PendingIntent; import android.content.IIntentSender; import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.os.IResultReceiver; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.SafeActivityOptions; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; /** * Helper class for {@link ActivityManagerService} responsible for managing pending intents. * *

This class uses {@link #mLock} to synchronize access to internal state and doesn't make use of * {@link ActivityManagerService} lock since there can be direct calls into this class from outside * AM. This helps avoid deadlocks. */ public class PendingIntentController { private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentController" : TAG_AM; private static final String TAG_MU = TAG + POSTFIX_MU; /** Lock for internal state. */ final Object mLock = new Object(); final Handler mH; ActivityManagerInternal mAmInternal; final UserController mUserController; final ActivityTaskManagerInternal mAtmInternal; /** Set of IntentSenderRecord objects that are currently active. */ final HashMap> mIntentSenderRecords = new HashMap<>(); PendingIntentController(Looper looper, UserController userController) { mH = new Handler(looper); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mUserController = userController; } void onActivityManagerInternalAdded() { synchronized (mLock) { mAmInternal = LocalServices.getService(ActivityManagerInternal.class); } } public PendingIntentRecord getIntentSender(int type, String packageName, int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions) { synchronized (mLock) { if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSender(): uid=" + callingUid); // We're going to be splicing together extras before sending, so we're // okay poking into any contained extras. if (intents != null) { for (int i = 0; i < intents.length; i++) { intents[i].setDefusable(true); } } Bundle.setDefusable(bOptions, true); final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0; final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0; final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0; flags &= ~(PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_UPDATE_CURRENT); PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, token, resultWho, requestCode, intents, resolvedTypes, flags, SafeActivityOptions.fromBundle(bOptions), userId); WeakReference ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec != null) { if (!cancelCurrent) { if (updateCurrent) { if (rec.key.requestIntent != null) { rec.key.requestIntent.replaceExtras(intents != null ? intents[intents.length - 1] : null); } if (intents != null) { intents[intents.length - 1] = rec.key.requestIntent; rec.key.allIntents = intents; rec.key.allResolvedTypes = resolvedTypes; } else { rec.key.allIntents = null; rec.key.allResolvedTypes = null; } } return rec; } makeIntentSenderCanceled(rec); mIntentSenderRecords.remove(key); } if (noCreate) { return rec; } rec = new PendingIntentRecord(this, key, callingUid); mIntentSenderRecords.put(key, rec.ref); return rec; } } boolean removePendingIntentsForPackage(String packageName, int userId, int appId, boolean doIt) { boolean didSomething = false; synchronized (mLock) { // Remove pending intents. For now we only do this when force stopping users, because // we have some problems when doing this for packages -- app widgets are not currently // cleaned up for such packages, so they can be left with bad pending intents. if (mIntentSenderRecords.size() <= 0) { return false; } Iterator> it = mIntentSenderRecords.values().iterator(); while (it.hasNext()) { WeakReference wpir = it.next(); if (wpir == null) { it.remove(); continue; } PendingIntentRecord pir = wpir.get(); if (pir == null) { it.remove(); continue; } if (packageName == null) { // Stopping user, remove all objects for the user. if (pir.key.userId != userId) { // Not the same user, skip it. continue; } } else { if (UserHandle.getAppId(pir.uid) != appId) { // Different app id, skip it. continue; } if (userId != UserHandle.USER_ALL && pir.key.userId != userId) { // Different user, skip it. continue; } if (!pir.key.packageName.equals(packageName)) { // Different package, skip it. continue; } } if (!doIt) { return true; } didSomething = true; it.remove(); makeIntentSenderCanceled(pir); if (pir.key.activity != null) { final Message m = PooledLambda.obtainMessage( PendingIntentController::clearPendingResultForActivity, this, pir.key.activity, pir.ref); mH.sendMessage(m); } } } return didSomething; } public void cancelIntentSender(IIntentSender sender) { if (!(sender instanceof PendingIntentRecord)) { return; } synchronized (mLock) { final PendingIntentRecord rec = (PendingIntentRecord) sender; try { final int uid = AppGlobals.getPackageManager().getPackageUid(rec.key.packageName, MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getCallingUserId()); if (!UserHandle.isSameApp(uid, Binder.getCallingUid())) { String msg = "Permission Denial: cancelIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " is not allowed to cancel package " + rec.key.packageName; Slog.w(TAG, msg); throw new SecurityException(msg); } } catch (RemoteException e) { throw new SecurityException(e); } cancelIntentSender(rec, true); } } public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) { synchronized (mLock) { makeIntentSenderCanceled(rec); mIntentSenderRecords.remove(rec.key); if (cleanActivity && rec.key.activity != null) { final Message m = PooledLambda.obtainMessage( PendingIntentController::clearPendingResultForActivity, this, rec.key.activity, rec.ref); mH.sendMessage(m); } } } void registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) { if (!(sender instanceof PendingIntentRecord)) { return; } boolean isCancelled; synchronized (mLock) { PendingIntentRecord pendingIntent = (PendingIntentRecord) sender; isCancelled = pendingIntent.canceled; if (!isCancelled) { pendingIntent.registerCancelListenerLocked(receiver); } } if (isCancelled) { try { receiver.send(Activity.RESULT_CANCELED, null); } catch (RemoteException e) { } } } void unregisterIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) { if (!(sender instanceof PendingIntentRecord)) { return; } synchronized (mLock) { ((PendingIntentRecord) sender).unregisterCancelListenerLocked(receiver); } } void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whitelistToken, long duration) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target); return; } synchronized (mLock) { ((PendingIntentRecord) target).setWhitelistDurationLocked(whitelistToken, duration); } } private void makeIntentSenderCanceled(PendingIntentRecord rec) { rec.canceled = true; final RemoteCallbackList callbacks = rec.detachCancelListenersLocked(); if (callbacks != null) { final Message m = PooledLambda.obtainMessage( PendingIntentController::handlePendingIntentCancelled, this, callbacks); mH.sendMessage(m); } final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class); ami.remove(new PendingIntent(rec)); } private void handlePendingIntentCancelled(RemoteCallbackList callbacks) { int N = callbacks.beginBroadcast(); for (int i = 0; i < N; i++) { try { callbacks.getBroadcastItem(i).send(Activity.RESULT_CANCELED, null); } catch (RemoteException e) { // Process is not longer running...whatever. } } callbacks.finishBroadcast(); // We have to clean up the RemoteCallbackList here, because otherwise it will // needlessly hold the enclosed callbacks until the remote process dies. callbacks.kill(); } private void clearPendingResultForActivity(IBinder activityToken, WeakReference pir) { mAtmInternal.clearPendingResultForActivity(activityToken, pir); } void dumpPendingIntents(PrintWriter pw, boolean dumpAll, String dumpPackage) { synchronized (mLock) { boolean printed = false; pw.println("ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)"); if (mIntentSenderRecords.size() > 0) { // Organize these by package name, so they are easier to read. final ArrayMap> byPackage = new ArrayMap<>(); final ArrayList> weakRefs = new ArrayList<>(); final Iterator> it = mIntentSenderRecords.values().iterator(); while (it.hasNext()) { WeakReference ref = it.next(); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec == null) { weakRefs.add(ref); continue; } if (dumpPackage != null && !dumpPackage.equals(rec.key.packageName)) { continue; } ArrayList list = byPackage.get(rec.key.packageName); if (list == null) { list = new ArrayList<>(); byPackage.put(rec.key.packageName, list); } list.add(rec); } for (int i = 0; i < byPackage.size(); i++) { ArrayList intents = byPackage.valueAt(i); printed = true; pw.print(" * "); pw.print(byPackage.keyAt(i)); pw.print(": "); pw.print(intents.size()); pw.println(" items"); for (int j = 0; j < intents.size(); j++) { pw.print(" #"); pw.print(j); pw.print(": "); pw.println(intents.get(j)); if (dumpAll) { intents.get(j).dump(pw, " "); } } } if (weakRefs.size() > 0) { printed = true; pw.println(" * WEAK REFS:"); for (int i = 0; i < weakRefs.size(); i++) { pw.print(" #"); pw.print(i); pw.print(": "); pw.println(weakRefs.get(i)); } } } if (!printed) { pw.println(" (nothing)"); } } } }