Merge \"Mark app pending intents in notification extras\" into nyc-dev
am: b2cd9c95bf
Change-Id: Idb515255f224c23d60a513713d9e4f93decd7a9c
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index eaea989..05f49c5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -39,6 +39,7 @@
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.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.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.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 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)}.
*/
@@ -1573,7 +1587,16 @@
/**
* 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();
@@ -1728,6 +1751,10 @@
}
}
+ 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++) {
@@ -1843,8 +1870,40 @@
/**
* 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 cb15392..cfa242b 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -242,6 +242,36 @@
}
/**
+ * 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 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 2631247..74dcc07 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 @@
}
/**
+ * 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 @@
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 9e9314f..d39e91f 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -390,6 +390,32 @@
}
/**
+ * 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 59de263..9209d3d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2598,7 +2598,6 @@
final long duration = LocalServices.getService(DeviceIdleController.LocalService.class)
.getNotificationWhitelistDuration();
- int size = 0;
if (notification.contentIntent != null) {
am.setPendingIntentWhitelistDuration(notification.contentIntent.getTarget(), duration);
}
@@ -2615,45 +2614,19 @@
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;