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

Commit aa07653d authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

Prevent apps to overlay other apps via toast windows

It was possible for apps to put toast type windows
that overlay other apps which toast winodws aren't
removed after a timeout.

Now for apps targeting SDK greater than N MR1 to add a
toast window one needs to have a special token. The token
is added by the notificatoion manager service only for
the lifetime of the shown toast and is then removed
including all windows associated with this token. This
prevents apps to add arbitrary toast windows.

Since legacy apps may rely on the ability to directly
add toasts we mitigate by allowing these apps to still
add such windows for unlimited duration if this app is
the currently focused one, i.e. the user interacts with
it then it can overlay itself, otherwise we make sure
these toast windows are removed after a timeout like
a toast would be.

We don't allow more that one toast window per UID being
added at a time which prevents 1) legacy apps to put the
same toast after a timeout to go around our new policy
of hiding toasts after a while; 2) modern apps to reuse
the passed token to add more than one window; Note that
the notification manager shows toasts one at a time.

bug:30150688

Change-Id: Ia1dae626bd9e22541be46edb072aa288eb1ae414
parent 041d39d7
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package android.app;

/** @hide */
oneway interface ITransientNotification {
    void show();
    void show(IBinder windowToken);
    void hide();
}
+1 −0
Original line number Diff line number Diff line
@@ -227,6 +227,7 @@ public class SurfaceView extends View {
        mSession = getWindowSession();
        mLayout.token = getWindowToken();
        mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
        mLayout.packageName = mContext.getOpPackageName();
        mViewVisibility = getVisibility() == VISIBLE;

        if (!mGlobalListenersAdded) {
+10 −6
Original line number Diff line number Diff line
@@ -1752,14 +1752,18 @@ public interface WindowManager extends ViewManager {
        public CharSequence accessibilityTitle;

        /**
         * Sets a timeout in milliseconds before which the window will be removed
         * Sets a timeout in milliseconds before which the window will be hidden
         * by the window manager. Useful for transient notifications like toasts
         * so we don't have to rely on client cooperation to ensure the window
         * is removed. Must be specified at window creation time.
         * is hidden. Must be specified at window creation time. Note that apps
         * are not prepared to handle their windows being removed without their
         * explicit request and may try to interact with the removed window
         * resulting in undefined behavior and crashes. Therefore, we do hide
         * such windows to prevent them from overlaying other apps.
         *
         * @hide
         */
        public long removeTimeoutMilliseconds = -1;
        public long hideTimeoutMilliseconds = -1;

        public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
@@ -1895,7 +1899,7 @@ public interface WindowManager extends ViewManager {
            out.writeInt(needsMenuKey);
            out.writeInt(accessibilityIdOfAnchor);
            TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
            out.writeLong(removeTimeoutMilliseconds);
            out.writeLong(hideTimeoutMilliseconds);
        }

        public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1949,7 +1953,7 @@ public interface WindowManager extends ViewManager {
            needsMenuKey = in.readInt();
            accessibilityIdOfAnchor = in.readInt();
            accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
            removeTimeoutMilliseconds = in.readLong();
            hideTimeoutMilliseconds = in.readLong();
        }

        @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2171,7 +2175,7 @@ public interface WindowManager extends ViewManager {
            }

            // This can't change, it's only set at window creation time.
            removeTimeoutMilliseconds = o.removeTimeoutMilliseconds;
            hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;

            return changes;
        }
+14 −12
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -326,13 +328,6 @@ public class Toast {
    }

    private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            @Override
            public void run() {
@@ -343,7 +338,13 @@ public class Toast {
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();
        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                IBinder token = (IBinder) msg.obj;
                handleShow(token);
            }
        };

        int mGravity;
        int mX, mY;
@@ -379,9 +380,9 @@ public class Toast {
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
            mHandler.obtainMessage(0, windowToken).sendToTarget();
        }

        /**
@@ -393,7 +394,7 @@ public class Toast {
            mHandler.post(mHide);
        }

        public void handleShow() {
        public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
@@ -422,8 +423,9 @@ public class Toast {
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                mParams.removeTimeoutMilliseconds = mDuration ==
                mParams.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
+23 −12
Original line number Diff line number Diff line
@@ -58,7 +58,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
@@ -93,7 +92,6 @@ import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -122,6 +120,8 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import android.view.WindowManager;
import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
@@ -138,6 +138,7 @@ import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
import com.android.server.notification.ManagedServices.UserProfiles;
@@ -193,7 +194,7 @@ public class NotificationManagerService extends SystemService {
    private static final int MESSAGE_RECONSIDER_RANKING = 1000;
    private static final int MESSAGE_RANKING_SORT = 1001;

    static final int LONG_DELAY = 3500; // 3.5 seconds
    static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
    static final int SHORT_DELAY = 2000; // 2 seconds

    static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
@@ -232,6 +233,7 @@ public class NotificationManagerService extends SystemService {
    @Nullable StatusBarManagerInternal mStatusBar;
    Vibrator mVibrator;
    private VrManagerInternal mVrManagerInternal;
    private WindowManagerInternal mWindowManagerInternal;

    final IBinder mForegroundToken = new Binder();
    private Handler mHandler;
@@ -452,13 +454,15 @@ public class NotificationManagerService extends SystemService {
        final String pkg;
        final ITransientNotification callback;
        int duration;
        Binder token;

        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
        {
        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration,
                    Binder token) {
            this.pid = pid;
            this.pkg = pkg;
            this.callback = callback;
            this.duration = duration;
            this.token = token;
        }

        void update(int duration) {
@@ -1125,6 +1129,7 @@ public class NotificationManagerService extends SystemService {
            mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
            mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
            mVrManagerInternal = getLocalService(VrManagerInternal.class);
            mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
            mZenModeHelper.onSystemReady();
        } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
            // This observer will force an update when observe is called, causing us to
@@ -1325,10 +1330,13 @@ public class NotificationManagerService extends SystemService {
                            }
                        }

                        record = new ToastRecord(callingPid, pkg, callback, duration);
                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token,
                                WindowManager.LayoutParams.TYPE_TOAST);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveLocked(callingPid);
                        keepProcessAliveIfNeededLocked(callingPid);
                    }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
@@ -2991,7 +2999,7 @@ public class NotificationManagerService extends SystemService {
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show();
                record.callback.show(record.token);
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
@@ -3002,7 +3010,7 @@ public class NotificationManagerService extends SystemService {
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveLocked(record.pid);
                keepProcessAliveIfNeededLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
@@ -3022,8 +3030,11 @@ public class NotificationManagerService extends SystemService {
            // don't worry about this, we're about to remove it from
            // the list anyway
        }
        mToastQueue.remove(index);
        keepProcessAliveLocked(record.pid);

        ToastRecord lastToast = mToastQueue.remove(index);
        mWindowManagerInternal.removeWindowToken(lastToast.token, true);

        keepProcessAliveIfNeededLocked(record.pid);
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
@@ -3067,7 +3078,7 @@ public class NotificationManagerService extends SystemService {
    }

    // lock on mToastQueue
    void keepProcessAliveLocked(int pid)
    void keepProcessAliveIfNeededLocked(int pid)
    {
        int toastCount = 0; // toasts from this pid
        ArrayList<ToastRecord> list = mToastQueue;
Loading