Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddb94889 authored by Svet Ganov's avatar Svet Ganov
Browse files

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
parent 5836c9e6
Loading
Loading
Loading
Loading
+62 −3
Original line number Diff line number Diff line
@@ -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<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);
+35 −0
Original line number Diff line number Diff line
@@ -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<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)}.
@@ -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
+37 −0
Original line number Diff line number Diff line
@@ -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<? 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) {
+26 −0
Original line number Diff line number Diff line
@@ -389,6 +389,32 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
        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 <var>array</var>
     * @param array The array whose contents are to be retrieved.
+6 −33
Original line number Diff line number Diff line
@@ -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,43 +2600,17 @@ 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());
            }
        }
        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,
            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);
                    }
                }
            }
        }
    }

    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 {