diff options
| -rw-r--r-- | core/java/android/app/Notification.java | 65 | ||||
| -rw-r--r-- | core/java/android/app/PendingIntent.java | 35 | ||||
| -rw-r--r-- | core/java/android/os/Parcel.java | 37 | ||||
| -rw-r--r-- | core/java/android/util/ArraySet.java | 26 | ||||
| -rw-r--r-- | services/core/java/com/android/server/notification/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 6fc1820c3c36..3c3da7804a11 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; @@ -758,6 +762,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<PendingIntent> 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<PendingIntent>) 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<this.actions.length; i++) { @@ -1819,8 +1846,40 @@ public class Notification implements Parcelable /** * Flatten this notification into a parcel. */ - public void writeToParcel(Parcel parcel, int flags) - { + public void writeToParcel(Parcel parcel, int flags) { + // We need to mark all pending intents getting into the notification + // system as being put there to later allow the notification ranker + // to launch them and by doing so add the app to the battery saver white + // list for a short period of time. The problem is that the system + // 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 = (extrasPendingIntents == null); + if (collectPendingIntents) { + PendingIntent.setOnMarshaledListener( + (PendingIntent intent, Parcel out, int outFlags) -> { + 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 cb15392b6a8a..cfa242be02aa 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -242,6 +242,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<OnMarshaledListener> 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)}. * Note that the activity will be started outside of the context of an @@ -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<PendingIntent> CREATOR diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 26312475297c..74dcc0787b3b 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; @@ -734,6 +736,21 @@ public final class Parcel { } /** + * Write an array set to the parcel. + * + * @param val The array set to write. + * + * @hide + */ + public void writeArraySet(@Nullable ArraySet<? extends Object> 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<? extends Object> readArraySet(ClassLoader loader) { + final int size = readInt(); + if (size < 0) { + return null; + } + ArraySet<Object> 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 9e9314fba4c4..d39e91fd98b2 100644 --- a/core/java/android/util/ArraySet.java +++ b/core/java/android/util/ArraySet.java @@ -390,6 +390,32 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { } /** + * 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 <var>array</var> * @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 73850de8681e..d0bd9816a62e 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; |