diff --git a/core/java/android/app/ITransientNotification.aidl b/core/java/android/app/ITransientNotification.aidl index 35b53a48e6e18..d5b3ed0a44d80 100644 --- a/core/java/android/app/ITransientNotification.aidl +++ b/core/java/android/app/ITransientNotification.aidl @@ -19,7 +19,7 @@ package android.app; /** @hide */ oneway interface ITransientNotification { - void show(); + void show(IBinder windowToken); void hide(); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 48189106f2c4a..b6955a82a3ed4 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -233,6 +233,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) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index fe2423093c881..4a20619b000fe 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1734,14 +1734,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); @@ -1877,7 +1881,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 CREATOR @@ -1931,7 +1935,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"}) @@ -2153,7 +2157,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; } diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 77626754264f1..eca10cb5e7fd5 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -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); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8b5942c85e4bb..de1d7a76f7d80 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -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. @@ -2987,7 +2995,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) { @@ -2998,7 +3006,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 { @@ -3018,8 +3026,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 @@ -3063,7 +3074,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 list = mToastQueue; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 09c2b2774f680..68c4960da6131 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -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 @@ -2229,6 +2232,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } 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) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8c5481d1a4d85..a4fcbb0a8ad71 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -200,6 +201,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; @@ -1867,6 +1869,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean reportNewConfig = false; WindowState attachedWindow = null; long origId; + final int callingUid = Binder.getCallingUid(); final int type = attrs.type; synchronized(mWindowMap) { @@ -1914,6 +1917,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); AppWindowToken atoken = null; + boolean addToastWindowRequiresToken = false; + if (token == null) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG_WM, "Attempted to add application window with unknown token " @@ -1950,6 +1955,15 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } + if (type == TYPE_TOAST) { + // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. + if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, + attachedWindow)) { + Slog.w(TAG_WM, "Attempted to add a toast window with unknown token " + + attrs.token + ". Aborting."); + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; + } + } token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { @@ -1999,6 +2013,15 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } + } else if (type == TYPE_TOAST) { + // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. + addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName, + callingUid, attachedWindow); + if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) { + Slog.w(TAG_WM, "Attempted to add a toast window with bad token " + + attrs.token + ". Aborting."); + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; + } } else if (type == TYPE_QS_DIALOG) { if (token.windowType != TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token " @@ -2043,6 +2066,35 @@ public class WindowManagerService extends IWindowManager.Stub win.openInputChannel(outInputChannel); } + // If adding a toast requires a token for this app we always schedule hiding + // toast windows to make sure they don't stick around longer then necessary. + // We hide instead of remove such windows as apps aren't prepared to handle + // windows being removed under them. + // If the app is older it can add toasts without a token and hence overlay + // other apps. To be maximally compatible with these apps we will hide the + // window after the toast timeout only if the focused window is from another + // UID, otherwise we allow unlimited duration. When a UID looses focus we + // schedule hiding all of its toast windows. + if (type == TYPE_TOAST) { + if (!canAddToastWindowForUid(getDefaultDisplayContentLocked(), callingUid)) { + Slog.w(TAG_WM, "Adding more than one toast window for UID at a time."); + return WindowManagerGlobal.ADD_DUPLICATE_ADD; + } + // Make sure this happens before we moved focus as one can make the + // toast focusable to force it not being hidden after the timeout. + // Focusable toasts are always timed out to prevent a focused app to + // show a focusable toasts while it has focus which will be kept on + // the screen after the activity goes away. + if (addToastWindowRequiresToken + || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0 + || mCurrentFocus == null + || mCurrentFocus.mOwnerUid != callingUid) { + mH.sendMessageDelayed( + mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win), + win.mAttrs.hideTimeoutMilliseconds); + } + } + // From now on, no exceptions or errors allowed! res = WindowManagerGlobal.ADD_OKAY; @@ -2181,11 +2233,6 @@ public class WindowManagerService extends IWindowManager.Stub if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true; } - if (attrs.removeTimeoutMilliseconds > 0) { - mH.sendMessageDelayed( - mH.obtainMessage(H.WINDOW_REMOVE_TIMEOUT, win), - attrs.removeTimeoutMilliseconds); - } } if (reportNewConfig) { @@ -2197,6 +2244,73 @@ public class WindowManagerService extends IWindowManager.Stub return res; } + private boolean canAddToastWindowForUid(DisplayContent displayContent, int uid) { + // We allow one toast window per UID being shown at a time. + WindowList windows = displayContent.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) { + return false; + } + } + return true; + } + + private boolean doesAddToastWindowRequireToken(String packageName, int callingUid, + WindowState attachedWindow) { + // Try using the target SDK of the root window + if (attachedWindow != null) { + WindowState currentWindow = attachedWindow; + while (currentWindow != null) { + if (currentWindow.mAppToken != null + && currentWindow.mAppToken.targetSdk > Build.VERSION_CODES.N_MR1) { + return true; + } + currentWindow = currentWindow.mAttachedWindow; + } + } else { + // Otherwise, look at the package + try { + ApplicationInfo appInfo = mContext.getPackageManager() + .getApplicationInfoAsUser(packageName, 0, + UserHandle.getUserId(callingUid)); + if (appInfo.uid != callingUid) { + throw new SecurityException("Package " + packageName + " not in UID " + + callingUid); + } + if (appInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + /* ignore */ + } + } + return false; + } + + private void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus, + WindowState newFocus) { + if (oldFocus == null || (newFocus != null && newFocus.mOwnerUid == oldFocus.mOwnerUid)) { + return; + } + final int lostFocusUid = oldFocus.mOwnerUid; + DisplayContent displayContent = oldFocus.getDisplayContent(); + WindowList windows = displayContent.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 (!mH.hasMessages(H.WINDOW_HIDE_TIMEOUT, window)) { + mH.sendMessageDelayed( + mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, window), + window.mAttrs.hideTimeoutMilliseconds); + } + } + } + } + /** * Returns true if we're done setting up any transitions. */ @@ -8121,7 +8235,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int NOTIFY_APP_TRANSITION_FINISHED = 49; public static final int NOTIFY_STARTING_WINDOW_DRAWN = 50; public static final int UPDATE_ANIMATION_SCALE = 51; - public static final int WINDOW_REMOVE_TIMEOUT = 52; + public static final int WINDOW_HIDE_TIMEOUT = 52; public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53; public static final int SEAMLESS_ROTATION_TIMEOUT = 54; @@ -8741,7 +8855,7 @@ public class WindowManagerService extends IWindowManager.Stub mAmInternal.notifyStartingWindowDrawn(); } break; - case WINDOW_REMOVE_TIMEOUT: { + case WINDOW_HIDE_TIMEOUT: { final WindowState window = (WindowState) msg.obj; synchronized(mWindowMap) { // TODO: This is all about fixing b/21693547 @@ -8752,8 +8866,11 @@ public class WindowManagerService extends IWindowManager.Stub // running under debugger) to crash (b/29105388). The windows will // eventually be removed when the client process finishes. // The best we can do for now is remove the FLAG_KEEP_SCREEN_ON - // and prevent the symptoms of b/21693547. + // and prevent the symptoms of b/21693547. Since apps don't + // support windows being removed under them we hide the window + // and it will be removed when the app dies. window.mAttrs.flags &= ~FLAG_KEEP_SCREEN_ON; + window.markPermanentlyHiddenLw(); window.setDisplayLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); } @@ -9783,6 +9900,11 @@ public class WindowManagerService extends IWindowManager.Stub adjustForImeIfNeeded(displayContent); + // We may need to schedule some toast windows to be removed. The + // toasts for an app that does not have input focus are removed + // within a timeout to prevent apps to redress other apps' UI. + scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); return true; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 25ad07ef07a76..7a3c07c6f3031 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -165,6 +165,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mPolicyVisibility = true; boolean mPolicyVisibilityAfterAnim = true; boolean mAppOpVisibility = true; + boolean mPermanentlyHidden; boolean mAppFreezing; boolean mAttachedHidden; // is our parent window hidden? boolean mWallpaperVisible; // for wallpaper, what was last vis report? @@ -1866,6 +1867,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { // Being hidden due to app op request. return false; } + if (mPermanentlyHidden) { + // Permanently hidden until the app exists as apps aren't prepared + // to handle their windows being removed from under them. + return false; + } if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { // Already showing. return false; @@ -1956,6 +1962,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } + public void markPermanentlyHiddenLw() { + if (!mPermanentlyHidden) { + mPermanentlyHidden = true; + hideLw(true, true); + } + } + public void pokeDrawLockLw(long timeout) { if (isVisibleOrAdding()) { if (mDrawLock == null) { @@ -2593,7 +2606,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.println(Integer.toHexString(mSystemUiVisibility)); } if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility - || mAttachedHidden) { + || mAttachedHidden || mPermanentlyHidden) { pw.print(prefix); pw.print("mPolicyVisibility="); pw.print(mPolicyVisibility); pw.print(" mPolicyVisibilityAfterAnim="); @@ -2601,6 +2614,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(" mAppOpVisibility="); pw.print(mAppOpVisibility); pw.print(" mAttachedHidden="); pw.println(mAttachedHidden); + pw.print(" mPermanentlyHidden="); pw.println(mPermanentlyHidden); } if (!mRelayoutCalled || mLayoutNeeded) { pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);