diff options
4 files changed, 160 insertions, 18 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 591dfcca3c88..44068dd2aed5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3047,10 +3047,9 @@ public class Notification implements Parcelable // cannot look into the extras as there may be parcelables there that // the platform does not know how to handle. To go around that we have // an explicit list of the pending intents in the extras bundle. - final boolean collectPendingIntents = (allPendingIntents == null); - if (collectPendingIntents) { - PendingIntent.setOnMarshaledListener( - (PendingIntent intent, Parcel out, int outFlags) -> { + PendingIntent.OnMarshaledListener addedListener = null; + if (allPendingIntents == null) { + addedListener = (PendingIntent intent, Parcel out, int outFlags) -> { if (parcel == out) { synchronized (this) { if (allPendingIntents == null) { @@ -3059,7 +3058,8 @@ public class Notification implements Parcelable allPendingIntents.add(intent); } } - }); + }; + PendingIntent.addOnMarshaledListener(addedListener); } try { // IMPORTANT: Add marshaling code in writeToParcelImpl as we @@ -3070,8 +3070,8 @@ public class Notification implements Parcelable parcel.writeArraySet(allPendingIntents); } } finally { - if (collectPendingIntents) { - PendingIntent.setOnMarshaledListener(null); + if (addedListener != null) { + PendingIntent.removeOnMarshaledListener(addedListener); } } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 705b5ee84d01..c7522b429dbc 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -64,6 +64,7 @@ import com.android.internal.os.IResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -391,11 +392,12 @@ public final class PendingIntent implements Parcelable { void onMarshaled(PendingIntent intent, Parcel parcel, int flags); } - private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener - = new ThreadLocal<>(); + private static final ThreadLocal<List<OnMarshaledListener>> sOnMarshaledListener = + ThreadLocal.withInitial(ArrayList::new); /** - * Registers an listener for pending intents being written to a parcel. + * Registers an listener for pending intents being written to a parcel. This replaces any + * listeners previously added. * * @param listener The listener, null to clear. * @@ -403,7 +405,27 @@ public final class PendingIntent implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static void setOnMarshaledListener(OnMarshaledListener listener) { - sOnMarshaledListener.set(listener); + final List<OnMarshaledListener> listeners = sOnMarshaledListener.get(); + listeners.clear(); + if (listener != null) { + listeners.add(listener); + } + } + + /** + * Adds a listener for pending intents being written to a parcel. + * @hide + */ + static void addOnMarshaledListener(OnMarshaledListener listener) { + sOnMarshaledListener.get().add(listener); + } + + /** + * Removes a listener for pending intents being written to a parcel. + * @hide + */ + static void removeOnMarshaledListener(OnMarshaledListener listener) { + sOnMarshaledListener.get().remove(listener); } private static void checkPendingIntent(int flags, @NonNull Intent intent, @@ -1451,11 +1473,11 @@ public final class PendingIntent implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeStrongBinder(mTarget.asBinder()); - OnMarshaledListener listener = sOnMarshaledListener.get(); - if (listener != null) { - listener.onMarshaled(this, out, flags); + final List<OnMarshaledListener> listeners = sOnMarshaledListener.get(); + final int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + listeners.get(i).onMarshaled(this, out, flags); } - } public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() { @@ -1483,9 +1505,10 @@ public final class PendingIntent implements Parcelable { @NonNull Parcel out) { out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null); if (sender != null) { - OnMarshaledListener listener = sOnMarshaledListener.get(); - if (listener != null) { - listener.onMarshaled(sender, out, 0 /* flags */); + final List<OnMarshaledListener> listeners = sOnMarshaledListener.get(); + final int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + listeners.get(i).onMarshaled(sender, out, 0 /* flags */); } } } diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index eba7f587bb8b..7c69e65fecd7 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -271,6 +271,54 @@ public class NotificationTest { } @Test + public void allPendingIntents_resilientToAnotherNotificationInExtras() { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent = createPendingIntent("action"); + Notification another = new Notification.Builder(mContext, "channel").build(); + Bundle bundleContainingAnotherNotification = new Bundle(); + bundleContainingAnotherNotification.putParcelable(null, another); + Notification source = new Notification.Builder(mContext, "channel") + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action", actionIntent).build()) + .setExtras(bundleContainingAnotherNotification) + .build(); + + Parcel p = Parcel.obtain(); + source.writeToParcel(p, 0); + p.setDataPosition(0); + Notification unparceled = new Notification(p); + + assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent); + } + + @Test + public void allPendingIntents_alsoInPublicVersion() { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent = createPendingIntent("action"); + PendingIntent publicContentIntent = createPendingIntent("publicContent"); + PendingIntent publicActionIntent = createPendingIntent("publicAction"); + Notification source = new Notification.Builder(mContext, "channel") + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action", actionIntent).build()) + .setPublicVersion(new Notification.Builder(mContext, "channel") + .setContentIntent(publicContentIntent) + .addAction(new Notification.Action.Builder( + null, "publicAction", publicActionIntent).build()) + .build()) + .build(); + + Parcel p = Parcel.obtain(); + source.writeToParcel(p, 0); + p.setDataPosition(0); + Notification unparceled = new Notification(p); + + assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent, + publicContentIntent, publicActionIntent); + assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent, + publicActionIntent); + } + + @Test public void messagingStyle_isGroupConversation() { mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P; Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name") diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a51079a48bf1..30180e8f43a7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -62,6 +62,8 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; @@ -83,6 +85,9 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; +import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; +import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; @@ -186,6 +191,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; @@ -716,6 +722,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @After public void assertAllWakeLocksReleased() { + waitForIdle(); // Finish async work. for (WakeLock wakeLock : mAcquiredWakeLocks) { assertThat(wakeLock.isHeld()).isFalse(); } @@ -12117,6 +12124,70 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old } + @Test + public void enqueueNotification_allowlistsPendingIntents() throws RemoteException { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent1 = createPendingIntent("action1"); + PendingIntent actionIntent2 = createPendingIntent("action2"); + Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action1", actionIntent1).build()) + .addAction(new Notification.Action.Builder(null, "action2", actionIntent2).build()) + .build(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1, + parcelAndUnparcel(n, Notification.CREATOR), mUserId); + + verify(mAmi, times(3)).setPendingIntentAllowlistDuration( + any(), any(), anyLong(), + eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED), + eq(REASON_NOTIFICATION_SERVICE), any()); + verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(), + any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); + } + + @Test + public void enqueueNotification_allowlistsPendingIntents_includingFromPublicVersion() + throws RemoteException { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent = createPendingIntent("action"); + PendingIntent publicContentIntent = createPendingIntent("publicContent"); + PendingIntent publicActionIntent = createPendingIntent("publicAction"); + Notification source = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action", actionIntent).build()) + .setPublicVersion(new Notification.Builder(mContext, "channel") + .setContentIntent(publicContentIntent) + .addAction(new Notification.Action.Builder( + null, "publicAction", publicActionIntent).build()) + .build()) + .build(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1, + parcelAndUnparcel(source, Notification.CREATOR), mUserId); + + verify(mAmi, times(4)).setPendingIntentAllowlistDuration( + any(), any(), anyLong(), + eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED), + eq(REASON_NOTIFICATION_SERVICE), any()); + verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(), + any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); + } + + private static <T extends Parcelable> T parcelAndUnparcel(T source, + Parcelable.Creator<T> creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } + private void setDpmAppOppsExemptFromDismissal(boolean isOn) { DeviceConfig.setProperty( DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, |