Permalink
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
  • Loading branch information...
sganov committed Aug 2, 2016
1 parent 041d39d commit aa07653d2eea38a7a5bda5944c8a353586916ae9
@@ -19,7 +19,7 @@ package android.app;
/** @hide */
oneway interface ITransientNotification {
void show();
void show(IBinder windowToken);
void hide();
}
@@ -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) {
@@ -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);
@@ -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
@@ -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"})
@@ -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;
}
@@ -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;
@@ -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() {
@@ -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;
@@ -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();
}
/**
@@ -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) {
@@ -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);
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -193,7 +194,7 @@
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 @@
@Nullable StatusBarManagerInternal mStatusBar;
Vibrator mVibrator;
private VrManagerInternal mVrManagerInternal;
private WindowManagerInternal mWindowManagerInternal;
final IBinder mForegroundToken = new Binder();
private Handler mHandler;
@@ -452,13 +454,15 @@ private boolean checkNotificationOp(String pkg, int uid) {
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 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
@@ -1325,10 +1330,13 @@ public void enqueueToast(String pkg, ITransientNotification callback, int durati
}
}
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 @@ 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) {
@@ -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 {
@@ -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
@@ -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;
@@ -301,6 +301,9 @@
/** 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
@@ -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) {
@@ -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;
@@ -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);
}
}
}
}
}
Oops, something went wrong.

0 comments on commit aa07653

Please sign in to comment.