Merge "Add audio sharing button and highlight audio sharing device." into main
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8022eb3..4aae7d1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4437,8 +4437,16 @@
         final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
                 && packageStateStopped);
         if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
+            final int cancelReason;
+            if (packageName == null) {
+                cancelReason = PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+            } else if (uninstalling) {
+                cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_UNINSTALLED;
+            } else {
+                cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+            }
             didSomething |= mPendingIntentController.removePendingIntentsForPackage(
-                    packageName, userId, appId, doit);
+                    packageName, userId, appId, doit, cancelReason);
         }
 
         if (doit) {
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index fb0d695..f336120 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -22,6 +22,8 @@
 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 static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -54,6 +56,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
+import com.android.server.am.PendingIntentRecord.CancellationReason;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.SafeActivityOptions;
 
@@ -191,7 +194,7 @@
                     }
                     return rec;
                 }
-                makeIntentSenderCanceled(rec);
+                makeIntentSenderCanceled(rec, CANCEL_REASON_SUPERSEDED);
                 mIntentSenderRecords.remove(key);
                 decrementUidStatLocked(rec);
             }
@@ -206,7 +209,7 @@
     }
 
     boolean removePendingIntentsForPackage(String packageName, int userId, int appId,
-            boolean doIt) {
+            boolean doIt, @CancellationReason int cancelReason) {
 
         boolean didSomething = false;
         synchronized (mLock) {
@@ -256,7 +259,7 @@
                 }
                 didSomething = true;
                 it.remove();
-                makeIntentSenderCanceled(pir);
+                makeIntentSenderCanceled(pir, cancelReason);
                 decrementUidStatLocked(pir);
                 if (pir.key.activity != null) {
                     final Message m = PooledLambda.obtainMessage(
@@ -289,13 +292,14 @@
             } catch (RemoteException e) {
                 throw new SecurityException(e);
             }
-            cancelIntentSender(rec, true);
+            cancelIntentSender(rec, true, CANCEL_REASON_OWNER_CANCELED);
         }
     }
 
-    public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) {
+    public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity,
+            @CancellationReason int cancelReason) {
         synchronized (mLock) {
-            makeIntentSenderCanceled(rec);
+            makeIntentSenderCanceled(rec, cancelReason);
             mIntentSenderRecords.remove(rec.key);
             decrementUidStatLocked(rec);
             if (cleanActivity && rec.key.activity != null) {
@@ -359,8 +363,10 @@
         }
     }
 
-    private void makeIntentSenderCanceled(PendingIntentRecord rec) {
+    private void makeIntentSenderCanceled(PendingIntentRecord rec,
+            @CancellationReason int cancelReason) {
         rec.canceled = true;
+        rec.cancelReason = cancelReason;
         final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();
         if (callbacks != null) {
             final Message m = PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 95e130e..da45a77 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -16,11 +16,13 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.START_SUCCESS;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
@@ -51,11 +53,15 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.expresslog.Counter;
 import com.android.server.wm.SafeActivityOptions;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.Objects;
 
@@ -71,12 +77,35 @@
     public static final int FLAG_BROADCAST_SENDER = 1 << 1;
     public static final int FLAG_SERVICE_SENDER = 1 << 2;
 
+    public static final int CANCEL_REASON_NULL = 0;
+    public static final int CANCEL_REASON_USER_STOPPED = 1 << 0;
+    public static final int CANCEL_REASON_OWNER_UNINSTALLED = 1 << 1;
+    public static final int CANCEL_REASON_OWNER_FORCE_STOPPED = 1 << 2;
+    public static final int CANCEL_REASON_OWNER_CANCELED = 1 << 3;
+    public static final int CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED = 1 << 4;
+    public static final int CANCEL_REASON_SUPERSEDED = 1 << 5;
+    public static final int CANCEL_REASON_ONE_SHOT_SENT = 1 << 6;
+
+    @IntDef({
+            CANCEL_REASON_NULL,
+            CANCEL_REASON_USER_STOPPED,
+            CANCEL_REASON_OWNER_UNINSTALLED,
+            CANCEL_REASON_OWNER_FORCE_STOPPED,
+            CANCEL_REASON_OWNER_CANCELED,
+            CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED,
+            CANCEL_REASON_SUPERSEDED,
+            CANCEL_REASON_ONE_SHOT_SENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CancellationReason {}
+
     final PendingIntentController controller;
     final Key key;
     final int uid;
     public final WeakReference<PendingIntentRecord> ref;
     boolean sent = false;
     boolean canceled = false;
+    @CancellationReason int cancelReason = CANCEL_REASON_NULL;
     /**
      * Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
      * milliseconds, Integer is allowlist type defined at
@@ -419,12 +448,22 @@
         SafeActivityOptions mergedOptions = null;
         synchronized (controller.mLock) {
             if (canceled) {
+                if (cancelReason == CANCEL_REASON_OWNER_FORCE_STOPPED
+                        && controller.mAmInternal.getUidProcessState(callingUid)
+                                == PROCESS_STATE_TOP) {
+                    Counter.logIncrementWithUid(
+                            "app.value_force_stop_cancelled_pi_sent_from_top_per_caller",
+                            callingUid);
+                    Counter.logIncrementWithUid(
+                            "app.value_force_stop_cancelled_pi_sent_from_top_per_owner",
+                            uid);
+                }
                 return ActivityManager.START_CANCELED;
             }
 
             sent = true;
             if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
-                controller.cancelIntentSender(this, true);
+                controller.cancelIntentSender(this, true, CANCEL_REASON_ONE_SHOT_SENT);
             }
 
             finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent();
@@ -687,6 +726,21 @@
         }
     }
 
+    @VisibleForTesting
+    static String cancelReasonToString(@CancellationReason int cancelReason) {
+        return switch (cancelReason) {
+            case CANCEL_REASON_NULL -> "NULL";
+            case CANCEL_REASON_USER_STOPPED -> "USER_STOPPED";
+            case CANCEL_REASON_OWNER_UNINSTALLED -> "OWNER_UNINSTALLED";
+            case CANCEL_REASON_OWNER_FORCE_STOPPED -> "OWNER_FORCE_STOPPED";
+            case CANCEL_REASON_OWNER_CANCELED -> "OWNER_CANCELED";
+            case CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED -> "HOSTING_ACTIVITY_DESTROYED";
+            case CANCEL_REASON_SUPERSEDED -> "SUPERSEDED";
+            case CANCEL_REASON_ONE_SHOT_SENT -> "ONE_SHOT_SENT";
+            default -> "UNKNOWN";
+        };
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("uid="); pw.print(uid);
                 pw.print(" packageName="); pw.print(key.packageName);
@@ -707,7 +761,8 @@
         }
         if (sent || canceled) {
             pw.print(prefix); pw.print("sent="); pw.print(sent);
-                    pw.print(" canceled="); pw.println(canceled);
+                    pw.print(" canceled="); pw.print(canceled);
+                    pw.print(" cancelReason="); pw.println(cancelReasonToString(cancelReason));
         }
         if (mAllowlistDuration != null) {
             pw.print(prefix);
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
@@ -67,7 +69,7 @@
         AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
     }
 
-    static void initialize(@NonNull Handler handler) {
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
         handler.post(() -> {
@@ -79,8 +81,16 @@
                             handler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     if (!sPerUserMap.contains(userId)) {
-                                        sPerUserMap.put(userId,
-                                                AdditionalSubtypeUtils.load(userId));
+                                        final AdditionalSubtypeMap additionalSubtypeMap =
+                                                AdditionalSubtypeUtils.load(userId);
+                                        sPerUserMap.put(userId, additionalSubtypeMap);
+                                        final InputMethodSettings settings =
+                                                InputMethodManagerService
+                                                        .queryInputMethodServicesInternal(context,
+                                                                userId,
+                                                                additionalSubtypeMap,
+                                                                DirectBootAwareness.AUTO);
+                                        InputMethodSettingsRepository.put(userId, settings);
                                     }
                                 }
                             });
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 03a85c4..e41b47f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -869,9 +869,17 @@
             if (!mSystemReady) {
                 return;
             }
-            mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                    AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                    DirectBootAwareness.AUTO);
+            for (int userId : mUserManagerInternal.getUserIds()) {
+                final InputMethodSettings settings = queryInputMethodServicesInternal(
+                                mContext,
+                                userId,
+                                AdditionalSubtypeMapRepository.get(userId),
+                                DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, settings);
+                if (userId == mSettings.getUserId()) {
+                    mSettings = settings;
+                }
+            }
             postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
             // If the locale is changed, needs to reset the default ime
             resetDefaultImeLocked(mContext);
@@ -1118,12 +1126,15 @@
                     AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                             settings.getMethodMap());
                 }
-
-                if (!isCurrentUser) {
+                if (isCurrentUser
+                        && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
                     return;
                 }
 
-                if (!(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
+                if (!isCurrentUser) {
                     return;
                 }
                 mSettings = queryInputMethodServicesInternal(mContext, userId,
@@ -1281,21 +1292,22 @@
 
     void onUnlockUser(@UserIdInt int userId) {
         synchronized (ImfLock.class) {
-            final int currentUserId = mSettings.getUserId();
             if (DEBUG) {
-                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
-            }
-            if (userId != currentUserId) {
-                return;
+                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
+                        + mSettings.getUserId());
             }
             if (!mSystemReady) {
                 return;
             }
-            mSettings = queryInputMethodServicesInternal(mContext, userId,
-                    AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
-            // We need to rebuild IMEs.
-            postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
-            updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+            final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                    userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+            InputMethodSettingsRepository.put(userId, newSettings);
+            if (mSettings.getUserId() == userId) {
+                mSettings = newSettings;
+                // We need to rebuild IMEs.
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+                updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+            }
         }
     }
 
@@ -1361,12 +1373,13 @@
 
             mShowOngoingImeSwitcherForPhones = false;
 
-            AdditionalSubtypeMapRepository.initialize(mHandler);
+            // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+            InputMethodSettingsRepository.initialize(mHandler, mContext);
+            AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
 
             final int userId = mActivityManagerInternal.getCurrentUserId();
 
-            // mSettings should be created before buildInputMethodListLocked
-            mSettings = InputMethodSettings.createEmptyMap(userId);
+            mSettings = InputMethodSettingsRepository.get(userId);
 
             mSwitchingController =
                     InputMethodSubtypeSwitchingController.createInstanceLocked(context,
@@ -1529,8 +1542,10 @@
         // and user switch would not happen at that time.
         resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
 
-        mSettings = queryInputMethodServicesInternal(mContext, newUserId,
-                AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+        final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+        InputMethodSettingsRepository.put(newUserId, newSettings);
+        mSettings = newSettings;
         postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
         if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
             // This is the first time of the user switch and
@@ -1612,9 +1627,11 @@
 
                 final String defaultImiId = mSettings.getSelectedInputMethod();
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-                mSettings = queryInputMethodServicesInternal(mContext, currentUserId,
-                        AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
                         DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(currentUserId, newSettings);
+                mSettings = newSettings;
                 postInputMethodSettingUpdatedLocked(
                         !imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 updateFromSettingsLocked(true);
@@ -4076,22 +4093,20 @@
 
             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
             final boolean isCurrentUser = (mSettings.getUserId() == userId);
-            final InputMethodSettings settings = isCurrentUser
-                    ? mSettings
-                    : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                            DirectBootAwareness.AUTO);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
                 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                         settings.getMethodMap());
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, AdditionalSubtypeMapRepository.get(userId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
                 if (isCurrentUser) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        mSettings = queryInputMethodServicesInternal(mContext,
-                                mSettings.getUserId(),
-                                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                                DirectBootAwareness.AUTO);
+                        mSettings = newSettings;
                         postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodSettingsRepository() {
+    }
+
+    @NonNull
+    @GuardedBy("ImfLock.class")
+    static InputMethodSettings get(@UserIdInt int userId) {
+        final InputMethodSettings obj = sPerUserMap.get(userId);
+        if (obj != null) {
+            return obj;
+        }
+        return InputMethodSettings.createEmptyMap(userId);
+    }
+
+    @GuardedBy("ImfLock.class")
+    static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+        sPerUserMap.put(userId, obj);
+    }
+
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        handler.post(() -> {
+            userManagerInternal.addUserLifecycleListener(
+                    new UserManagerInternal.UserLifecycleListener() {
+                        @Override
+                        public void onUserRemoved(UserInfo user) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    sPerUserMap.remove(userId);
+                                }
+                            });
+                        }
+                    });
+            synchronized (ImfLock.class) {
+                for (int userId : userManagerInternal.getUserIds()) {
+                    final InputMethodSettings settings =
+                            InputMethodManagerService.queryInputMethodServicesInternal(
+                                    context,
+                                    userId,
+                                    AdditionalSubtypeMapRepository.get(userId),
+                                    DirectBootAwareness.AUTO);
+                    sPerUserMap.put(userId, settings);
+                }
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83745ed..42373aa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4260,7 +4260,8 @@
                 PendingIntentRecord rec = apr.get();
                 if (rec != null) {
                     mAtmService.mPendingIntentController.cancelIntentSender(rec,
-                            false /* cleanActivity */);
+                            false /* cleanActivity */,
+                            PendingIntentRecord.CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED);
                 }
             }
             pendingResults = null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 783971a..89b48ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,9 +16,18 @@
 
 package com.android.server.am;
 
+import static android.os.Process.INVALID_UID;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_NULL;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_ONE_SHOT_SENT;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +43,7 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.os.Looper;
+import android.os.UserHandle;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -54,6 +64,7 @@
     private static final String TEST_PACKAGE_NAME = "test-package-1";
     private static final String TEST_FEATURE_ID = "test-feature-1";
     private static final int TEST_CALLING_UID = android.os.Process.myUid();
+    private static final int TEST_USER_ID = 0;
     private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
 
     @Mock
@@ -92,7 +103,7 @@
 
     private PendingIntentRecord createPendingIntentRecord(int flags) {
         return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
-                TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, 0, null, null, 0,
+                TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0,
                 TEST_INTENTS, null, flags, null);
     }
 
@@ -126,6 +137,54 @@
                 piCaptor.getValue().getTarget());
     }
 
+    @Test
+    public void testCancellationReason() {
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            assertCancelReason(CANCEL_REASON_NULL, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            mPendingIntentController.cancelIntentSender(pir);
+            assertCancelReason(CANCEL_REASON_OWNER_CANCELED, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+            assertCancelReason(CANCEL_REASON_SUPERSEDED, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+            pir.send(0, null, null, null, null, null, null);
+            assertCancelReason(CANCEL_REASON_ONE_SHOT_SENT, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            mPendingIntentController.removePendingIntentsForPackage(TEST_PACKAGE_NAME,
+                    TEST_USER_ID, UserHandle.getAppId(TEST_CALLING_UID), true,
+                    CANCEL_REASON_OWNER_FORCE_STOPPED);
+            assertCancelReason(CANCEL_REASON_OWNER_FORCE_STOPPED, pir.cancelReason);
+        }
+
+        {
+            final PendingIntentRecord pir = createPendingIntentRecord(0);
+            mPendingIntentController.removePendingIntentsForPackage(null,
+                    TEST_USER_ID, INVALID_UID, true,
+                    CANCEL_REASON_USER_STOPPED);
+            assertCancelReason(CANCEL_REASON_USER_STOPPED, pir.cancelReason);
+        }
+    }
+
+    private void assertCancelReason(int expectedReason, int actualReason) {
+        final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
+                + "; Actual: " + cancelReasonToString(actualReason);
+        assertEquals(errMsg, expectedReason, actualReason);
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {