From ddb948896ca7059161e09d0063b3332352772c0a Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Thu, 23 Jun 2016 19:55:24 -0700 Subject: [PATCH] Mark app pending intents in notification extras We need to make every peniding intent that went in the notification system to allow special handling of such intents when fired by a notification listener. If a pending intent from a notification is sent from a notification listener, we white-list the source app to run in data saver mode for a short period of time. The problem is that actions and the notificaion can have extras which bundles may contain pending intents but the system cannot look into the bundles as they may contain custom parcelable objects. To address this we keep a list of all pending intents in the notification allowing the system to access them without touching the bundle. Currently the pending intents are written to the parcel twice, once in the bundle and once as the explicit list. We can come up with a scheme to optimize this but since pending itents are just a binder pointer it is not worth the excecise. bug:29480440 Change-Id: I7328a47017ca226117adf7054900836619f5679b --- core/java/android/app/Notification.java | 65 ++++++++++++++++++- core/java/android/app/PendingIntent.java | 35 ++++++++++ core/java/android/os/Parcel.java | 37 +++++++++++ core/java/android/util/ArraySet.java | 26 ++++++++ .../NotificationManagerService.java | 39 ++--------- 5 files changed, 166 insertions(+), 36 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6fc1820c3c361..3c3da7804a11f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -39,6 +39,7 @@ import android.media.AudioManager; import android.media.session.MediaSession; import android.net.Uri; import android.os.BadParcelableException; +import android.os.BaseBundle; import android.os.Build; import android.os.Bundle; import android.os.Parcel; @@ -53,6 +54,7 @@ import android.text.style.AbsoluteSizeSpan; import android.text.style.CharacterStyle; import android.text.style.RelativeSizeSpan; import android.text.style.TextAppearanceSpan; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.Gravity; @@ -63,6 +65,7 @@ import android.widget.ProgressBar; import android.widget.RemoteViews; import com.android.internal.R; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.NotificationColorUtil; import java.lang.annotation.Retention; @@ -70,6 +73,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -757,6 +761,16 @@ public class Notification implements Parcelable */ public Bundle extras = new Bundle(); + /** + * All pending intents in the notification extras (notification extras, actions extras, + * and remote input extras) as the system needs to be able to access them but touching + * the extras bundle in the system process is not safe because the bundle may contain + * custom parcelable objects. + * + * @hide + */ + public ArraySet extrasPendingIntents; + /** * {@link #extras} key: this is the title of the notification, * as supplied to {@link Builder#setContentTitle(CharSequence)}. @@ -1549,7 +1563,16 @@ public class Notification implements Parcelable /** * Unflatten the notification from a parcel. */ - public Notification(Parcel parcel) + @SuppressWarnings("unchecked") + public Notification(Parcel parcel) { + // IMPORTANT: Add unmarshaling code in readFromParcel as the pending + // intents in extras are always written as the last entry. + readFromParcelImpl(parcel); + // Must be read last! + extrasPendingIntents = (ArraySet) parcel.readArraySet(null); + } + + private void readFromParcelImpl(Parcel parcel) { int version = parcel.readInt(); @@ -1704,6 +1727,10 @@ public class Notification implements Parcelable } } + if (!ArrayUtils.isEmpty(extrasPendingIntents)) { + that.extrasPendingIntents = new ArraySet<>(extrasPendingIntents); + } + if (this.actions != null) { that.actions = new Action[this.actions.length]; for(int i=0; i { + if (parcel == out) { + if (extrasPendingIntents == null) { + extrasPendingIntents = new ArraySet<>(); + } + extrasPendingIntents.add(intent); + } + }); + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the pacel. + writeToParcelImpl(parcel, flags); + // Must be written last! + parcel.writeArraySet(extrasPendingIntents); + } finally { + if (collectPendingIntents) { + PendingIntent.setOnMarshaledListener(null); + } + } + } + + private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); parcel.writeLong(when); diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index cb15392b6a8a1..cfa242be02aa8 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -241,6 +241,36 @@ public final class PendingIntent implements Parcelable { } } + /** + * Listener for observing when pending intents are written to a parcel. + * + * @hide + */ + public interface OnMarshaledListener { + /** + * Called when a pending intent is written to a parcel. + * + * @param intent The pending intent. + * @param parcel The parcel to which it was written. + * @param flags The parcel flags when it was written. + */ + void onMarshaled(PendingIntent intent, Parcel parcel, int flags); + } + + private static final ThreadLocal sOnMarshaledListener + = new ThreadLocal<>(); + + /** + * Registers an listener for pending intents being written to a parcel. + * + * @param listener The listener, null to clear. + * + * @hide + */ + public static void setOnMarshaledListener(OnMarshaledListener listener) { + sOnMarshaledListener.set(listener); + } + /** * Retrieve a PendingIntent that will start a new activity, like calling * {@link Context#startActivity(Intent) Context.startActivity(Intent)}. @@ -1016,6 +1046,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); + } + } public static final Parcelable.Creator CREATOR diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 26312475297c3..74dcc0787b3bd 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -17,8 +17,10 @@ package android.os; import android.annotation.IntegerRes; +import android.annotation.Nullable; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Size; import android.util.SizeF; @@ -733,6 +735,21 @@ public final class Parcel { writeArrayMapInternal(val); } + /** + * Write an array set to the parcel. + * + * @param val The array set to write. + * + * @hide + */ + public void writeArraySet(@Nullable ArraySet val) { + final int size = (val != null) ? val.size() : -1; + writeInt(size); + for (int i = 0; i < size; i++) { + writeValue(val.valueAt(i)); + } + } + /** * Flatten a Bundle into the parcel at the current dataPosition(), * growing dataCapacity() if needed. @@ -2735,6 +2752,26 @@ public final class Parcel { readArrayMapInternal(outVal, N, loader); } + /** + * Reads an array set. + * + * @param loader The class loader to use. + * + * @hide + */ + public @Nullable ArraySet readArraySet(ClassLoader loader) { + final int size = readInt(); + if (size < 0) { + return null; + } + ArraySet result = new ArraySet<>(size); + for (int i = 0; i < size; i++) { + Object value = readValue(loader); + result.append(value); + } + return result; + } + private void readListInternal(List outVal, int N, ClassLoader loader) { while (N > 0) { diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java index 9e9314fba4c4b..d39e91fd98b2e 100644 --- a/core/java/android/util/ArraySet.java +++ b/core/java/android/util/ArraySet.java @@ -389,6 +389,32 @@ public final class ArraySet implements Collection, Set { return true; } + /** + * Special fast path for appending items to the end of the array without validation. + * The array must already be large enough to contain the item. + * @hide + */ + public void append(E value) { + final int index = mSize; + final int hash = value == null ? 0 + : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode()); + if (index >= mHashes.length) { + throw new IllegalStateException("Array is full"); + } + if (index > 0 && mHashes[index - 1] > hash) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "New hash " + hash + + " is before end of array hash " + mHashes[index - 1] + + " at index " + index, e); + add(value); + return; + } + mSize = index + 1; + mHashes[index] = hash; + mArray[index] = value; + } + /** * Perform a {@link #add(Object)} of all values in array * @param array The array whose contents are to be retrieved. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 73850de8681e4..d0bd9816a62e8 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2584,7 +2584,6 @@ public class NotificationManagerService extends SystemService { final long duration = LocalServices.getService(DeviceIdleController.LocalService.class) .getNotificationWhitelistDuration(); - int size = 0; if (notification.contentIntent != null) { am.setPendingIntentWhitelistDuration(notification.contentIntent.getTarget(), duration); } @@ -2601,45 +2600,19 @@ public class NotificationManagerService extends SystemService { continue; } am.setPendingIntentWhitelistDuration(action.actionIntent.getTarget(), duration); - setPendingIntentWhitelistDuration(am, duration, action.getExtras()); - final RemoteInput[] remoteInputs = action.getRemoteInputs(); - if (remoteInputs != null) { - for (RemoteInput remoteInput : remoteInputs) { - setPendingIntentWhitelistDuration(am, duration, remoteInput.getExtras()); - } - } } } - } - - private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration, - Bundle extras) { - for (String key : extras.keySet()) { - final Object value = extras.get(key); - if (value instanceof Parcelable) { - setPendingIntentWhitelistDuration(am, duration, (Parcelable) value); - } else if (value instanceof Parcelable[]) { - for (Parcelable parcelable : (Parcelable[]) value) { - setPendingIntentWhitelistDuration(am, duration, parcelable); - } - } else if (value instanceof List) { - for (Object element : (List ) value) { - if (element instanceof Parcelable) { - setPendingIntentWhitelistDuration(am, duration, (Parcelable) element); - } + if (notification.extrasPendingIntents != null) { + final int intentCount = notification.extrasPendingIntents.size(); + for (int i = 0; i < intentCount; i++) { + PendingIntent pendingIntent = notification.extrasPendingIntents.valueAt(i); + if (pendingIntent != null) { + am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration); } } } } - private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration, - Parcelable parcelable) { - if (parcelable instanceof PendingIntent) { - am.setPendingIntentWhitelistDuration(((PendingIntent) parcelable).getTarget(), - duration); - } - } - private class EnqueueNotificationRunnable implements Runnable { private final NotificationRecord r; private final int userId;