| /* |
| * 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. |
| * |
| * <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; |
| |
| /** 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<>(); |
| |
| 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<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); |
| } |
| 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<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); |
| 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<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)); |
| } |
| } |
| } |
| |
| if (!printed) { |
| pw.println(" (nothing)"); |
| } |
| } |
| } |
| } |