diff options
14 files changed, 819 insertions, 21 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9d2bb58cdf0f..2f12fecb7fd0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -311,6 +311,16 @@ public final class ViewRootImpl implements ViewParent, SystemProperties.getBoolean("persist.wm.debug.client_transient", false); /** + * Whether the client (system UI) is handling the immersive confirmation window. If + * {@link CLIENT_TRANSIENT} is set to true, the immersive confirmation window will always be the + * client instance and this flag will be ignored. Otherwise, the immersive confirmation window + * can be switched freely by this flag. + * @hide + */ + public static final boolean CLIENT_IMMERSIVE_CONFIRMATION = + SystemProperties.getBoolean("persist.wm.debug.client_immersive_confirmation", false); + + /** * Whether the client should compute the window frame on its own. * @hide */ diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d702367965a1..f5a5c4007055 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3098,6 +3098,16 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16; /** + * Flag to indicate that this window is a immersive mode confirmation window. The window + * should be ignored when calculating insets control. This is used for prompt window + * triggered by insets visibility changes. If it can take over the insets control, the + * visibility will change unexpectedly and the window may dismiss itself. Power button panic + * handling will be disabled when this window exists. + * @hide + */ + public static final int PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW = 1 << 17; + + /** * Flag to indicate that any window added by an application process that is of type * {@link #TYPE_TOAST} or that requires * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when @@ -3241,6 +3251,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME, PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS, PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, + PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, @@ -3325,6 +3336,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, name = "SUSTAINED_PERFORMANCE_MODE"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW, + equals = PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW, + name = "IMMERSIVE_CONFIRMATION_WINDOW"), + @ViewDebug.FlagToString( mask = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, equals = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index d2564fb9c268..c6f5086b8346 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -63,6 +63,19 @@ oneway interface IStatusBar void cancelPreloadRecentApps(); void showScreenPinningRequest(int taskId); + /** + * Notify system UI the immersive prompt should be dismissed as confirmed, and the confirmed + * status should be saved without user clicking on the button. This could happen when a user + * swipe on the edge with the confirmation prompt showing. + */ + void confirmImmersivePrompt(); + + /** + * Notify system UI the immersive mode changed. This shall be removed when client immersive is + * enabled. + */ + void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode); + void dismissKeyboardShortcutsMenu(); void toggleKeyboardShortcutsMenu(int deviceId); diff --git a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml index 4029702ec6b4..32e88ab22b91 100644 --- a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml +++ b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml @@ -17,7 +17,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > - <solid android:color="@android:color/white" /> + <solid android:color="?android:attr/colorBackground" /> <size android:height="56dp" diff --git a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml index e3c7d0ce89aa..12c3e23bf0a0 100644 --- a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml +++ b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml @@ -17,7 +17,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > - <solid android:color="#80ffffff" /> + <solid android:color="?android:attr/colorBackground" /> <size android:height="76dp" diff --git a/packages/SystemUI/res/layout/immersive_mode_cling.xml b/packages/SystemUI/res/layout/immersive_mode_cling.xml index bfb8184ee044..e6529b9aa9a1 100644 --- a/packages/SystemUI/res/layout/immersive_mode_cling.xml +++ b/packages/SystemUI/res/layout/immersive_mode_cling.xml @@ -58,7 +58,7 @@ android:paddingStart="48dp" android:paddingTop="40dp" android:text="@string/immersive_cling_title" - android:textColor="@android:color/white" + android:textColor="?android:attr/textColorPrimaryInverse" android:textSize="24sp" /> <TextView @@ -70,7 +70,7 @@ android:paddingStart="48dp" android:paddingTop="12.6dp" android:text="@string/immersive_cling_description" - android:textColor="@android:color/white" + android:textColor="?android:attr/textColorPrimaryInverse" android:textSize="16sp" /> <Button @@ -85,7 +85,7 @@ android:paddingEnd="8dp" android:paddingStart="8dp" android:text="@string/immersive_cling_positive" - android:textColor="@android:color/white" + android:textColor="?android:attr/textColorPrimaryInverse" android:textSize="14sp" /> </RelativeLayout> diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index b1f513d0945c..6e70cf17825e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -48,6 +48,7 @@ import com.android.systemui.reardisplay.RearDisplayDialogController import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher +import com.android.systemui.statusbar.ImmersiveModeConfirmation import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController import com.android.systemui.statusbar.phone.LockscreenWallpaper @@ -159,6 +160,12 @@ abstract class SystemUICoreStartableModule { @ClassKey(Recents::class) abstract fun bindRecents(sysui: Recents): CoreStartable + /** Inject into ImmersiveModeConfirmation. */ + @Binds + @IntoMap + @ClassKey(ImmersiveModeConfirmation::class) + abstract fun bindImmersiveModeConfirmation(sysui: ImmersiveModeConfirmation): CoreStartable + /** Inject into RingtonePlayer. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 92df78bac17f..6304c1ea2635 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -168,7 +168,10 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT; private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT; private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT; - + private static final int MSG_SETTING_CHANGED = 74 << MSG_SHIFT; + private static final int MSG_LOCK_TASK_MODE_CHANGED = 75 << MSG_SHIFT; + private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT; + private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -498,6 +501,16 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#showMediaOutputSwitcher */ default void showMediaOutputSwitcher(String packageName) {} + + /** + * @see IStatusBar#confirmImmersivePrompt + */ + default void confirmImmersivePrompt() {} + + /** + * @see IStatusBar#immersiveModeChanged + */ + default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {} } @VisibleForTesting @@ -783,6 +796,23 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override + public void confirmImmersivePrompt() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_CONFIRM_IMMERSIVE_PROMPT).sendToTarget(); + } + } + + @Override + public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) { + synchronized (mLock) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = rootDisplayAreaId; + args.argi2 = isImmersiveMode ? 1 : 0; + mHandler.obtainMessage(MSG_IMMERSIVE_CHANGED, args).sendToTarget(); + } + } + + @Override public void appTransitionPending(int displayId) { appTransitionPending(displayId, false /* forced */); } @@ -1810,6 +1840,19 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).showMediaOutputSwitcher(clientPackageName); } break; + case MSG_CONFIRM_IMMERSIVE_PROMPT: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).confirmImmersivePrompt(); + } + break; + case MSG_IMMERSIVE_CHANGED: + args = (SomeArgs) msg.obj; + int rootDisplayAreaId = args.argi1; + boolean isImmersiveMode = args.argi2 != 0; + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java new file mode 100644 index 000000000000..a7ec02ff43c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.StatusBarManager.DISABLE_BACK; +import static android.app.StatusBarManager.DISABLE_HOME; +import static android.app.StatusBarManager.DISABLE_RECENT; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION; +import static android.view.ViewRootImpl.CLIENT_TRANSIENT; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; +import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.ContentObserver; +import android.graphics.Insets; +import android.graphics.PixelFormat; +import android.graphics.drawable.ColorDrawable; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.service.vr.IVrManager; +import android.service.vr.IVrStateCallbacks; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowInsets; +import android.view.WindowInsets.Type; +import android.view.WindowManager; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.CoreStartable; +import com.android.systemui.R; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.TaskStackChangeListeners; +import com.android.systemui.util.settings.SecureSettings; + +import javax.inject.Inject; + +/** + * Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden + * entering immersive mode. + */ +public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Callbacks, + TaskStackChangeListener { + private static final String TAG = "ImmersiveModeConfirm"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution + private static final String CONFIRMED = "confirmed"; + private static final int IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE = + WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; + + private static boolean sConfirmed; + private final SecureSettings mSecureSettings; + + private Context mDisplayContext; + private final Context mSysUiContext; + private final Handler mHandler = new H(Looper.getMainLooper()); + private long mShowDelayMs = 0L; + private final IBinder mWindowToken = new Binder(); + private final CommandQueue mCommandQueue; + + private ClingWindowView mClingWindow; + /** The last {@link WindowManager} that is used to add the confirmation window. */ + @Nullable + private WindowManager mWindowManager; + /** + * The WindowContext that is registered with {@link #mWindowManager} with options to specify the + * {@link RootDisplayArea} to attach the confirmation window. + */ + @Nullable + private Context mWindowContext; + /** + * The root display area feature id that the {@link #mWindowContext} is attaching to. + */ + private int mWindowContextRootDisplayAreaId = FEATURE_UNDEFINED; + // Local copy of vr mode enabled state, to avoid calling into VrManager with + // the lock held. + private boolean mVrModeEnabled = false; + private boolean mCanSystemBarsBeShownByUser = true; + private int mLockTaskState = LOCK_TASK_MODE_NONE; + private boolean mNavBarEmpty; + + private ContentObserver mContentObserver; + + @Inject + public ImmersiveModeConfirmation(Context context, CommandQueue commandQueue, + SecureSettings secureSettings) { + mSysUiContext = context; + final Display display = mSysUiContext.getDisplay(); + mDisplayContext = display.getDisplayId() == DEFAULT_DISPLAY + ? mSysUiContext : mSysUiContext.createDisplayContext(display); + mCommandQueue = commandQueue; + mSecureSettings = secureSettings; + } + + boolean loadSetting(int currentUserId) { + final boolean wasConfirmed = sConfirmed; + sConfirmed = false; + if (DEBUG) Log.d(TAG, String.format("loadSetting() currentUserId=%d", currentUserId)); + String value = null; + try { + value = mSecureSettings.getStringForUser(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, + UserHandle.USER_CURRENT); + sConfirmed = CONFIRMED.equals(value); + if (DEBUG) Log.d(TAG, "Loaded sConfirmed=" + sConfirmed); + } catch (Throwable t) { + Log.w(TAG, "Error loading confirmations, value=" + value, t); + } + return sConfirmed != wasConfirmed; + } + + private static void saveSetting(Context context) { + if (DEBUG) Log.d(TAG, "saveSetting()"); + try { + final String value = sConfirmed ? CONFIRMED : null; + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, + value, + UserHandle.USER_CURRENT); + if (DEBUG) Log.d(TAG, "Saved value=" + value); + } catch (Throwable t) { + Log.w(TAG, "Error saving confirmations, sConfirmed=" + sConfirmed, t); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + if (displayId != mSysUiContext.getDisplayId()) { + return; + } + mHandler.removeMessages(H.SHOW); + mHandler.removeMessages(H.HIDE); + IVrManager vrManager = IVrManager.Stub.asInterface( + ServiceManager.getService(Context.VR_SERVICE)); + if (vrManager != null) { + try { + vrManager.unregisterListener(mVrStateCallbacks); + } catch (RemoteException ex) { + } + } + mCommandQueue.removeCallback(this); + } + + private void onSettingChanged(int currentUserId) { + final boolean changed = loadSetting(currentUserId); + // Remove the window if the setting changes to be confirmed. + if (changed && sConfirmed) { + mHandler.sendEmptyMessage(H.HIDE); + } + } + + @Override + public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) { + mHandler.removeMessages(H.SHOW); + if (isImmersiveMode) { + if (DEBUG) Log.d(TAG, "immersiveModeChanged() sConfirmed=" + sConfirmed); + boolean userSetupComplete = (mSecureSettings.getIntForUser( + Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0); + + if ((DEBUG_SHOW_EVERY_TIME || !sConfirmed) + && userSetupComplete + && !mVrModeEnabled + && mCanSystemBarsBeShownByUser + && !mNavBarEmpty + && !UserManager.isDeviceInDemoMode(mDisplayContext) + && (mLockTaskState != LOCK_TASK_MODE_LOCKED)) { + final Message msg = mHandler.obtainMessage( + H.SHOW); + msg.arg1 = rootDisplayAreaId; + mHandler.sendMessageDelayed(msg, mShowDelayMs); + } + } else { + mHandler.sendEmptyMessage(H.HIDE); + } + } + + @Override + public void disable(int displayId, int disableFlag, int disableFlag2, boolean animate) { + if (mSysUiContext.getDisplayId() != displayId) { + return; + } + final int disableNavigationBar = (DISABLE_HOME | DISABLE_BACK | DISABLE_RECENT); + mNavBarEmpty = (disableFlag & disableNavigationBar) == disableNavigationBar; + } + + @Override + public void confirmImmersivePrompt() { + if (mClingWindow != null) { + if (DEBUG) Log.d(TAG, "confirmImmersivePrompt()"); + mHandler.post(mConfirm); + } + } + + private void handleHide() { + if (mClingWindow != null) { + if (DEBUG) Log.d(TAG, "Hiding immersive mode confirmation"); + if (mWindowManager != null) { + try { + mWindowManager.removeView(mClingWindow); + } catch (WindowManager.InvalidDisplayException e) { + Log.w(TAG, "Fail to hide the immersive confirmation window because of " + + e); + } + mWindowManager = null; + mWindowContext = null; + } + mClingWindow = null; + } + } + + private WindowManager.LayoutParams getClingWindowLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT); + lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars()); + // Trusted overlay so touches outside the touchable area are allowed to pass through + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY + | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; + lp.setTitle("ImmersiveModeConfirmation"); + lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; + lp.token = getWindowToken(); + return lp; + } + + private FrameLayout.LayoutParams getBubbleLayoutParams() { + return new FrameLayout.LayoutParams( + mSysUiContext.getResources().getDimensionPixelSize( + R.dimen.immersive_mode_cling_width), + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL | Gravity.TOP); + } + + /** + * @return the window token that's used by all ImmersiveModeConfirmation windows. + */ + IBinder getWindowToken() { + return mWindowToken; + } + + @Override + public void start() { + if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { + mCommandQueue.addCallback(this); + + final Resources r = mSysUiContext.getResources(); + mShowDelayMs = r.getInteger(R.integer.dock_enter_exit_duration) * 3L; + mCanSystemBarsBeShownByUser = !r.getBoolean( + R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean( + R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction); + IVrManager vrManager = IVrManager.Stub.asInterface( + ServiceManager.getService(Context.VR_SERVICE)); + if (vrManager != null) { + try { + mVrModeEnabled = vrManager.getVrModeState(); + vrManager.registerListener(mVrStateCallbacks); + mVrStateCallbacks.onVrStateChanged(mVrModeEnabled); + } catch (RemoteException e) { + // Ignore, we cannot do anything if we failed to access vr manager. + } + } + TaskStackChangeListeners.getInstance().registerTaskStackListener(this); + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + onSettingChanged(mSysUiContext.getUserId()); + } + }; + + // Register to listen for changes in Settings.Secure settings. + mSecureSettings.registerContentObserverForUser( + Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver, + UserHandle.USER_CURRENT); + mSecureSettings.registerContentObserverForUser( + Settings.Secure.USER_SETUP_COMPLETE, mContentObserver, + UserHandle.USER_CURRENT); + } + } + + private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { + @Override + public void onVrStateChanged(boolean enabled) { + mVrModeEnabled = enabled; + if (mVrModeEnabled) { + mHandler.removeMessages(H.SHOW); + mHandler.sendEmptyMessage(H.HIDE); + } + } + }; + + private class ClingWindowView extends FrameLayout { + private static final int BGCOLOR = 0x80000000; + private static final int OFFSET_DP = 96; + private static final int ANIMATION_DURATION = 250; + + private final Runnable mConfirm; + private final ColorDrawable mColor = new ColorDrawable(0); + private final Interpolator mInterpolator; + private ValueAnimator mColorAnim; + private ViewGroup mClingLayout; + + private Runnable mUpdateLayoutRunnable = new Runnable() { + @Override + public void run() { + if (mClingLayout != null && mClingLayout.getParent() != null) { + mClingLayout.setLayoutParams(getBubbleLayoutParams()); + } + } + }; + + private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = + new ViewTreeObserver.OnComputeInternalInsetsListener() { + private final int[] mTmpInt2 = new int[2]; + + @Override + public void onComputeInternalInsets( + ViewTreeObserver.InternalInsetsInfo inoutInfo) { + // Set touchable region to cover the cling layout. + mClingLayout.getLocationInWindow(mTmpInt2); + inoutInfo.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + inoutInfo.touchableRegion.set( + mTmpInt2[0], + mTmpInt2[1], + mTmpInt2[0] + mClingLayout.getWidth(), + mTmpInt2[1] + mClingLayout.getHeight()); + } + }; + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + post(mUpdateLayoutRunnable); + } + } + }; + + ClingWindowView(Context context, Runnable confirm) { + super(context); + mConfirm = confirm; + setBackground(mColor); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + mInterpolator = AnimationUtils + .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + DisplayMetrics metrics = new DisplayMetrics(); + mContext.getDisplay().getMetrics(metrics); + float density = metrics.density; + + getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); + + // create the confirmation cling + mClingLayout = (ViewGroup) + View.inflate(mSysUiContext, R.layout.immersive_mode_cling, null); + + TypedArray ta = mDisplayContext.obtainStyledAttributes( + new int[]{android.R.attr.colorAccent}); + int colorAccent = ta.getColor(0, 0); + ta.recycle(); + mClingLayout.setBackgroundColor(colorAccent); + ImageView expandMore = mClingLayout.findViewById(R.id.immersive_cling_ic_expand_more); + if (expandMore != null) { + expandMore.setImageTintList(ColorStateList.valueOf(colorAccent)); + } + ImageView lightBgCirc = mClingLayout.findViewById(R.id.immersive_cling_back_bg_light); + if (lightBgCirc != null) { + // Set transparency to 50% + lightBgCirc.setImageAlpha(128); + } + + final Button ok = mClingLayout.findViewById(R.id.ok); + ok.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mConfirm.run(); + } + }); + addView(mClingLayout, getBubbleLayoutParams()); + + if (ActivityManager.isHighEndGfx()) { + final View cling = mClingLayout; + cling.setAlpha(0f); + cling.setTranslationY(-OFFSET_DP * density); + + postOnAnimation(new Runnable() { + @Override + public void run() { + cling.animate() + .alpha(1f) + .translationY(0) + .setDuration(ANIMATION_DURATION) + .setInterpolator(mInterpolator) + .withLayer() + .start(); + + mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR); + mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final int c = (Integer) animation.getAnimatedValue(); + mColor.setColor(c); + } + }); + mColorAnim.setDuration(ANIMATION_DURATION); + mColorAnim.setInterpolator(mInterpolator); + mColorAnim.start(); + } + }); + } else { + mColor.setColor(BGCOLOR); + } + + mContext.registerReceiver(mReceiver, + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); + } + + @Override + public void onDetachedFromWindow() { + mContext.unregisterReceiver(mReceiver); + } + + @Override + public boolean onTouchEvent(MotionEvent motion) { + return true; + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + // we will be hiding the nav bar, so layout as if it's already hidden + return new WindowInsets.Builder(insets).setInsets( + Type.systemBars(), Insets.NONE).build(); + } + } + + /** + * To get window manager for the display. + * + * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the + * confirmation window. + */ + @NonNull + private WindowManager createWindowManager(int rootDisplayAreaId) { + if (mWindowManager != null) { + throw new IllegalStateException( + "Must not create a new WindowManager while there is an existing one"); + } + // Create window context to specify the RootDisplayArea + final Bundle options = getOptionsForWindowContext(rootDisplayAreaId); + mWindowContextRootDisplayAreaId = rootDisplayAreaId; + mWindowContext = mDisplayContext.createWindowContext( + IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options); + mWindowManager = mWindowContext.getSystemService(WindowManager.class); + return mWindowManager; + } + + /** + * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window. + * {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}. + */ + @Nullable + private Bundle getOptionsForWindowContext(int rootDisplayAreaId) { + // In case we don't care which root display area the window manager is specifying. + if (rootDisplayAreaId == FEATURE_UNDEFINED) { + return null; + } + + final Bundle options = new Bundle(); + options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId); + return options; + } + + private void handleShow(int rootDisplayAreaId) { + if (mClingWindow != null) { + if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) { + if (DEBUG) Log.d(TAG, "Immersive mode confirmation has already been shown"); + return; + } else { + // Hide the existing confirmation before show a new one in the new root. + if (DEBUG) Log.d(TAG, "Immersive mode confirmation was shown in a different root"); + handleHide(); + } + } + if (DEBUG) Log.d(TAG, "Showing immersive mode confirmation"); + mClingWindow = new ClingWindowView(mDisplayContext, mConfirm); + // show the confirmation + final WindowManager.LayoutParams lp = getClingWindowLayoutParams(); + try { + createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); + } catch (WindowManager.InvalidDisplayException e) { + Log.w(TAG, "Fail to show the immersive confirmation window because of " + e); + } + } + + private final Runnable mConfirm = new Runnable() { + @Override + public void run() { + if (DEBUG) Log.d(TAG, "mConfirm.run()"); + if (!sConfirmed) { + sConfirmed = true; + saveSetting(mDisplayContext); + } + handleHide(); + } + }; + + private final class H extends Handler { + private static final int SHOW = 1; + private static final int HIDE = 2; + + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { + return; + } + switch(msg.what) { + case SHOW: + handleShow(msg.arg1); + break; + case HIDE: + handleHide(); + break; + } + } + } + + @Override + public void onLockTaskModeChanged(int lockTaskState) { + mLockTaskState = lockTaskState; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 1643e174ee13..b04d5d3d44e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -517,6 +517,21 @@ public class CommandQueueTest extends SysuiTestCase { } @Test + public void testConfirmImmersivePrompt() { + mCommandQueue.confirmImmersivePrompt(); + waitForIdleSync(); + verify(mCallbacks).confirmImmersivePrompt(); + } + + @Test + public void testImmersiveModeChanged() { + final int displayAreaId = 10; + mCommandQueue.immersiveModeChanged(displayAreaId, true); + waitForIdleSync(); + verify(mCallbacks).immersiveModeChanged(displayAreaId, true); + } + + @Test public void testShowRearDisplayDialog() { final int currentBaseState = 1; mCommandQueue.showRearDisplayDialog(currentBaseState); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index efd8b6d9a943..a5123311d499 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -140,6 +140,20 @@ public interface StatusBarManagerInternal { boolean showShutdownUi(boolean isReboot, String requestString); /** + * Notify system UI the immersive prompt should be dismissed as confirmed, and the confirmed + * status should be saved without user clicking on the button. This could happen when a user + * swipe on the edge with the confirmation prompt showing. + */ + void confirmImmersivePrompt(); + + /** + * Notify System UI that the system get into or exit immersive mode. + * @param rootDisplayAreaId The changed display area Id. + * @param isImmersiveMode {@code true} if the display area get into immersive mode. + */ + void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode); + + /** * Show a rotation suggestion that a user may approve to rotate the screen. * * @param rotation rotation suggestion diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index cc849b6fbf91..40e9c1305f01 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -27,6 +27,7 @@ import static android.app.StatusBarManager.NavBarMode; import static android.app.StatusBarManager.SessionFlags; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.ViewRootImpl.CLIENT_TRANSIENT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; import android.Manifest; @@ -638,6 +639,31 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return false; } + @Override + public void confirmImmersivePrompt() { + if (mBar == null) { + return; + } + try { + mBar.confirmImmersivePrompt(); + } catch (RemoteException ex) { + } + } + + @Override + public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) { + if (mBar == null) { + return; + } + if (!CLIENT_TRANSIENT) { + // Only call from here when the client transient is not enabled. + try { + mBar.immersiveModeChanged(rootDisplayAreaId, isImmersiveMode); + } catch (RemoteException ex) { + } + } + } + // TODO(b/118592525): support it per display if necessary. @Override public void onProposedRotationChanged(int rotation, boolean isValid) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 2717a6a8ab04..354b0db77382 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -22,6 +22,7 @@ import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE; import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS; import static android.view.InsetsFrameProvider.SOURCE_DISPLAY; import static android.view.InsetsFrameProvider.SOURCE_FRAME; +import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION; import static android.view.ViewRootImpl.CLIENT_TRANSIENT; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; @@ -38,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; @@ -194,6 +196,8 @@ public class DisplayPolicy { private final ScreenshotHelper mScreenshotHelper; private final Object mServiceAcquireLock = new Object(); + private long mPanicTime; + private final long mPanicThresholdMs; private StatusBarManagerInternal mStatusBarManagerInternal; @Px @@ -246,6 +250,8 @@ public class DisplayPolicy { private volatile boolean mKeyguardDrawComplete; private volatile boolean mWindowManagerDrawComplete; + private boolean mImmersiveConfirmationWindowExists; + private WindowState mStatusBar = null; private volatile WindowState mNotificationShade; private WindowState mNavigationBar = null; @@ -402,6 +408,7 @@ public class DisplayPolicy { mCanSystemBarsBeShownByUser = !r.getBoolean( R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean( R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction); + mPanicThresholdMs = r.getInteger(R.integer.config_immersive_mode_confirmation_panic); mAccessibilityManager = (AccessibilityManager) mContext.getSystemService( Context.ACCESSIBILITY_SERVICE); @@ -623,8 +630,12 @@ public class DisplayPolicy { }; displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener); displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener); - mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper, - mService.mVrModeEnabled, mCanSystemBarsBeShownByUser); + if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { + mImmersiveModeConfirmation = null; + } else { + mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper, + mService.mVrModeEnabled, mCanSystemBarsBeShownByUser); + } // TODO: Make it can take screenshot on external display mScreenshotHelper = displayContent.isDefaultDisplay @@ -1075,6 +1086,9 @@ public class DisplayPolicy { mNavigationBar = win; break; } + if ((attrs.privateFlags & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) { + mImmersiveConfirmationWindowExists = true; + } if (attrs.providedInsets != null) { for (int i = attrs.providedInsets.length - 1; i >= 0; i--) { final InsetsFrameProvider provider = attrs.providedInsets[i]; @@ -1234,6 +1248,9 @@ public class DisplayPolicy { } } mInsetsSourceWindowsExceptIme.remove(win); + if ((win.mAttrs.privateFlags & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) { + mImmersiveConfirmationWindowExists = false; + } } WindowState getStatusBar() { @@ -2171,7 +2188,11 @@ public class DisplayPolicy { } } } - mImmersiveModeConfirmation.confirmCurrentPrompt(); + if (CLIENT_IMMERSIVE_CONFIRMATION || CLIENT_TRANSIENT) { + mStatusBarManagerInternal.confirmImmersivePrompt(); + } else { + mImmersiveModeConfirmation.confirmCurrentPrompt(); + } } boolean isKeyguardShowing() { @@ -2221,7 +2242,8 @@ public class DisplayPolicy { // Immersive mode confirmation should never affect the system bar visibility, otherwise // it will unhide the navigation bar and hide itself. - if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) { + if ((winCandidate.getAttrs().privateFlags + & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) { if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) { // Let notification shade control the system bar visibility. winCandidate = mNotificationShade; @@ -2389,9 +2411,16 @@ public class DisplayPolicy { // The immersive confirmation window should be attached to the immersive window root. final RootDisplayArea root = win.getRootDisplayArea(); final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId; - mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, isImmersiveMode, - mService.mPolicy.isUserSetupComplete(), - isNavBarEmpty(disableFlags)); + if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { + mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, + isImmersiveMode, + mService.mPolicy.isUserSetupComplete(), + isNavBarEmpty(disableFlags)); + } else { + // TODO (b/277290737): Move this to the client side, instead of using a proxy. + callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(rootDisplayAreaId, + isImmersiveMode)); + } } // Show transient bars for panic if needed. @@ -2604,16 +2633,39 @@ public class DisplayPolicy { void onPowerKeyDown(boolean isScreenOn) { // Detect user pressing the power button in panic when an application has // taken over the whole screen. - boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn, - SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow), - isNavBarEmpty(mLastDisableFlags)); + boolean panic = false; + if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { + panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn, + SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow), + isNavBarEmpty(mLastDisableFlags)); + } else { + panic = isPowerKeyDownPanic(isScreenOn, SystemClock.elapsedRealtime(), + isImmersiveMode(mSystemUiControllingWindow), isNavBarEmpty(mLastDisableFlags)); + } if (panic) { mHandler.post(mHiddenNavPanic); } } + private boolean isPowerKeyDownPanic(boolean isScreenOn, long time, boolean inImmersiveMode, + boolean navBarEmpty) { + if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) { + // turning the screen back on within the panic threshold + return !mImmersiveConfirmationWindowExists; + } + if (isScreenOn && inImmersiveMode && !navBarEmpty) { + // turning the screen off, remember if we were in immersive mode + mPanicTime = time; + } else { + mPanicTime = 0; + } + return false; + } + void onVrStateChangedLw(boolean enabled) { - mImmersiveModeConfirmation.onVrStateChangedLw(enabled); + if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { + mImmersiveModeConfirmation.onVrStateChangedLw(enabled); + } } /** @@ -2626,7 +2678,9 @@ public class DisplayPolicy { * {@link ActivityManager#LOCK_TASK_MODE_PINNED}. */ public void onLockTaskStateChangedLw(int lockTaskState) { - mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState); + if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { + mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState); + } } /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */ @@ -2643,7 +2697,11 @@ public class DisplayPolicy { } boolean onSystemUiSettingsChanged() { - return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId); + if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { + return false; + } else { + return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId); + } } /** @@ -2857,7 +2915,9 @@ public class DisplayPolicy { mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener); mHandler.post(mGestureNavigationSettingsObserver::unregister); mHandler.post(mForceShowNavBarSettingsObserver::unregister); - mImmersiveModeConfirmation.release(); + if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { + mImmersiveModeConfirmation.release(); + } if (mService.mPointerLocationEnabled) { setPointerLocationEnabled(false); } diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java index 56edde09f747..bd08dff9481b 100644 --- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.ViewRootImpl.CLIENT_TRANSIENT; import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID; @@ -229,7 +230,8 @@ public class ImmersiveModeConfirmation { lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars()); // Trusted overlay so touches outside the touchable area are allowed to pass through lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS - | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY + | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; lp.setTitle("ImmersiveModeConfirmation"); lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; lp.token = getWindowToken(); @@ -469,6 +471,9 @@ public class ImmersiveModeConfirmation { @Override public void handleMessage(Message msg) { + if (CLIENT_TRANSIENT) { + return; + } switch(msg.what) { case SHOW: handleShow(msg.arg1); |