Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Prevent apps to overlay other apps via toast windows
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sganov committed Sep 2, 2016
1 parent 041d39d commit aa07653
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 40 deletions.
2 changes: 1 addition & 1 deletion core/java/android/app/ITransientNotification.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package android.app;

/** @hide */
oneway interface ITransientNotification {
void show();
void show(IBinder windowToken);
void hide();
}

1 change: 1 addition & 0 deletions core/java/android/view/SurfaceView.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ protected void onAttachedToWindow() {
mSession = getWindowSession();
mLayout.token = getWindowToken();
mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
mLayout.packageName = mContext.getOpPackageName();
mViewVisibility = getVisibility() == VISIBLE;

if (!mGlobalListenersAdded) {
Expand Down
16 changes: 10 additions & 6 deletions core/java/android/view/WindowManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1752,14 +1752,18 @@ public static boolean mayUseInputMethod(int flags) {
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);
Expand Down Expand Up @@ -1895,7 +1899,7 @@ public void writeToParcel(Parcel out, int parcelableFlags) {
out.writeInt(needsMenuKey);
out.writeInt(accessibilityIdOfAnchor);
TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
out.writeLong(removeTimeoutMilliseconds);
out.writeLong(hideTimeoutMilliseconds);
}

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

@SuppressWarnings({"PointlessBitwiseExpression"})
Expand Down Expand Up @@ -2171,7 +2175,7 @@ public final int copyFrom(LayoutParams o) {
}

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

return changes;
}
Expand Down
26 changes: 14 additions & 12 deletions core/java/android/widget/Toast.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
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;
Expand Down Expand Up @@ -326,13 +328,6 @@ static private INotificationManager getService() {
}

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() {
Expand All @@ -343,7 +338,13 @@ public void run() {
};

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;
Expand Down Expand Up @@ -379,9 +380,9 @@ public void run() {
* 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();
}

/**
Expand All @@ -393,7 +394,7 @@ public void hide() {
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) {
Expand Down Expand Up @@ -422,8 +423,9 @@ public void handleShow() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
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;
Expand Down Expand Up @@ -93,7 +92,6 @@
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;
Expand Down Expand Up @@ -122,6 +120,8 @@
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;
Expand All @@ -138,6 +138,7 @@
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;
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -452,13 +454,15 @@ private static final class ToastRecord
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) {
Expand Down Expand Up @@ -1125,6 +1129,7 @@ public void onBootPhase(int phase) {
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
Expand Down Expand Up @@ -1325,10 +1330,13 @@ record = mToastQueue.get(index);
}
}

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.
Expand Down Expand Up @@ -2991,7 +2999,7 @@ void showNextToastLocked() {
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) {
Expand All @@ -3002,7 +3010,7 @@ void showNextToastLocked() {
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
Expand All @@ -3022,8 +3030,11 @@ void cancelToastLocked(int index) {
// 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
Expand Down Expand Up @@ -3067,7 +3078,7 @@ int indexOfToastLocked(String pkg, ITransientNotification callback)
}

// lock on mToastQueue
void keepProcessAliveLocked(int pid)
void keepProcessAliveIfNeededLocked(int pid)
{
int toastCount = 0; // toasts from this pid
ArrayList<ToastRecord> list = mToastQueue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;

/** Amount of time (in milliseconds) a toast window can be shown. */
public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds

/**
* Lock protecting internal state. Must not call out into window
* manager with lock held. (This lock will be acquired in places
Expand Down Expand Up @@ -2229,9 +2232,22 @@ public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
break;

case TYPE_SCREENSHOT:
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
break;

case TYPE_TOAST:
// While apps should use the dedicated toast APIs to add such windows
// it possible legacy apps to add the window directly. Therefore, we
// make windows added directly by the app behave as a toast as much
// as possible in terms of timeout and animation.
if (attrs.hideTimeoutMilliseconds < 0
|| attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
}
attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
break;
}

if (attrs.type != TYPE_STATUS_BAR) {
Expand Down
40 changes: 40 additions & 0 deletions services/core/java/com/android/server/wm/DisplayContent.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;

import android.app.ActivityManager.StackId;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.os.Build;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.Display;
Expand Down Expand Up @@ -684,4 +689,39 @@ void overridePlayingAppAnimationsLw(Animation a) {
mStacks.get(i).overridePlayingAppAnimations(a);
}
}

boolean canAddToastWindowForUid(int uid) {
// We allow one toast window per UID being shown at a time.
WindowList windows = getWindowList();
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
WindowState window = windows.get(i);
if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
&& !window.mPermanentlyHidden && !window.mAnimatingExit) {
return false;
}
}
return true;
}

void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus,
WindowState newFocus) {
if (oldFocus == null || (newFocus != null && newFocus.mOwnerUid == oldFocus.mOwnerUid)) {
return;
}
final int lostFocusUid = oldFocus.mOwnerUid;
WindowList windows = getWindowList();
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
WindowState window = windows.get(i);
if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == lostFocusUid) {
if (!mService.mH.hasMessages(WindowManagerService.H.WINDOW_HIDE_TIMEOUT, window)) {
mService.mH.sendMessageDelayed(
mService.mH.obtainMessage(
WindowManagerService.H.WINDOW_HIDE_TIMEOUT, window),
window.mAttrs.hideTimeoutMilliseconds);
}
}
}
}
}
Loading

0 comments on commit aa07653

Please sign in to comment.