| /* |
| * 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.annotation.Nullable; |
| 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 android.util.SparseArray; |
| import android.util.SparseIntArray; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.os.IResultReceiver; |
| import com.android.internal.util.RingBuffer; |
| 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.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| |
| /** |
| * Helper class for {@link ActivityManagerService} responsible for managing pending intents. |
| * |
| * <p>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; |
| |
| /** @see {@link #mRecentIntentsPerUid}. */ |
| private static final int RECENT_N = 10; |
| |
| /** 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<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords |
| = new HashMap<>(); |
| |
| /** The number of PendingIntentRecord per uid */ |
| @GuardedBy("mLock") |
| private final SparseIntArray mIntentsPerUid = new SparseIntArray(); |
| |
| /** The recent PendingIntentRecord, up to {@link #RECENT_N} per uid */ |
| @GuardedBy("mLock") |
| private final SparseArray<RingBuffer<String>> mRecentIntentsPerUid = new SparseArray<>(); |
| |
| private final ActivityManagerConstants mConstants; |
| |
| PendingIntentController(Looper looper, UserController userController, |
| ActivityManagerConstants constants) { |
| mH = new Handler(looper); |
| mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); |
| mUserController = userController; |
| mConstants = constants; |
| } |
| |
| void onActivityManagerInternalAdded() { |
| synchronized (mLock) { |
| mAmInternal = LocalServices.getService(ActivityManagerInternal.class); |
| } |
| } |
| |
| public PendingIntentRecord getIntentSender(int type, String packageName, |
| @Nullable String featureId, 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, featureId, |
| token, resultWho, requestCode, intents, resolvedTypes, flags, |
| SafeActivityOptions.fromBundle(bOptions), userId); |
| WeakReference<PendingIntentRecord> 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); |
| decrementUidStatLocked(rec); |
| } |
| if (noCreate) { |
| return rec; |
| } |
| rec = new PendingIntentRecord(this, key, callingUid); |
| mIntentSenderRecords.put(key, rec.ref); |
| incrementUidStatLocked(rec); |
| 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<WeakReference<PendingIntentRecord>> it |
| = mIntentSenderRecords.values().iterator(); |
| while (it.hasNext()) { |
| WeakReference<PendingIntentRecord> 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); |
| decrementUidStatLocked(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); |
| decrementUidStatLocked(rec); |
| 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<IResultReceiver> 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<IResultReceiver> 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<PendingIntentRecord> 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<String, ArrayList<PendingIntentRecord>> byPackage = new ArrayMap<>(); |
| final ArrayList<WeakReference<PendingIntentRecord>> weakRefs = new ArrayList<>(); |
| final Iterator<WeakReference<PendingIntentRecord>> it |
| = mIntentSenderRecords.values().iterator(); |
| while (it.hasNext()) { |
| WeakReference<PendingIntentRecord> 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<PendingIntentRecord> 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<PendingIntentRecord> 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)); |
| } |
| } |
| } |
| |
| final int sizeOfIntentsPerUid = mIntentsPerUid.size(); |
| if (sizeOfIntentsPerUid > 0) { |
| for (int i = 0; i < sizeOfIntentsPerUid; i++) { |
| pw.print(" * UID: "); |
| pw.print(mIntentsPerUid.keyAt(i)); |
| pw.print(" total: "); |
| pw.println(mIntentsPerUid.valueAt(i)); |
| } |
| } |
| |
| if (!printed) { |
| pw.println(" (nothing)"); |
| } |
| } |
| } |
| |
| /** |
| * Increment the number of the PendingIntentRecord for the given uid, log a warning |
| * if there are too many for this uid already. |
| */ |
| @GuardedBy("mLock") |
| void incrementUidStatLocked(final PendingIntentRecord pir) { |
| final int uid = pir.uid; |
| final int idx = mIntentsPerUid.indexOfKey(uid); |
| int newCount = 1; |
| if (idx >= 0) { |
| newCount = mIntentsPerUid.valueAt(idx) + 1; |
| mIntentsPerUid.setValueAt(idx, newCount); |
| } else { |
| mIntentsPerUid.put(uid, newCount); |
| } |
| |
| // If the number is within the range [threshold - N + 1, threshold], log it into buffer |
| final int lowBound = mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N + 1; |
| RingBuffer<String> recentHistory = null; |
| if (newCount == lowBound) { |
| recentHistory = new RingBuffer(String.class, RECENT_N); |
| mRecentIntentsPerUid.put(uid, recentHistory); |
| } else if (newCount > lowBound && newCount <= mConstants.PENDINGINTENT_WARNING_THRESHOLD) { |
| recentHistory = mRecentIntentsPerUid.get(uid); |
| } |
| if (recentHistory == null) { |
| return; |
| } |
| |
| recentHistory.append(pir.key.toString()); |
| |
| // Output the log if we are hitting the threshold |
| if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD) { |
| Slog.wtf(TAG, "Too many PendingIntent created for uid " + uid |
| + ", recent " + RECENT_N + ": " + Arrays.toString(recentHistory.toArray())); |
| // Clear the buffer, as we don't want to spam the log when the numbers |
| // are jumping up and down around the threshold. |
| mRecentIntentsPerUid.remove(uid); |
| } |
| } |
| |
| /** |
| * Decrement the number of the PendingIntentRecord for the given uid. |
| */ |
| @GuardedBy("mLock") |
| void decrementUidStatLocked(final PendingIntentRecord pir) { |
| final int uid = pir.uid; |
| final int idx = mIntentsPerUid.indexOfKey(uid); |
| if (idx >= 0) { |
| final int newCount = mIntentsPerUid.valueAt(idx) - 1; |
| // If we are going below the low threshold, no need to keep logs. |
| if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N) { |
| mRecentIntentsPerUid.delete(uid); |
| } |
| if (newCount == 0) { |
| mIntentsPerUid.removeAt(idx); |
| } else { |
| mIntentsPerUid.setValueAt(idx, newCount); |
| } |
| } |
| } |
| } |