Move ImmersiveModeConfirmation to system UI (4/n)

This change move the WM side immersive mode confirmation window to the
system UI side. The major change of the immersive mode confirmation mode
to accomodate system UI side logic includes:

1. Power key panic is handled purely in DisplayPolicy, with a tracking
   of existance of the immersive confirmation window.
2. Introduce another layout params private flag to identify the
   immersive mode confiramtion mode.
3. CLIENT_* flags handling.
4. Vr and release handling, and most of the settings and display change
   handling moved to system UI.
5. Updated the text color of the window to match the themed background
   color.

Introduce CLIENT_IMMERSIVE_CONFIRMATION flag to control whether client
or the server side of immersive confirmation window shall be used. The
flag will only be in effect if the CLIENT_TRANSIENT flag is not true,
otherwise, the client side confirmation window will always be used.

Bug: 277290737
Test: build
Test: enable CLIENT_TRANSIENT and check the existance of the immersive
mode confirmation window.

Change-Id: I99f6097cb867f9808aecf4c7105289550001f452
This commit is contained in:
Yunfan Chen
2023-05-25 17:38:28 +09:00
parent 66b0bf75ab
commit 1db1cf3cb6
14 changed files with 819 additions and 21 deletions

View File

@@ -310,6 +310,16 @@ public final class ViewRootImpl implements ViewParent,
public static final boolean CLIENT_TRANSIENT =
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

View File

@@ -3097,6 +3097,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
@@ -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,
@@ -3324,6 +3335,10 @@ public interface WindowManager extends ViewManager {
mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
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,

View File

@@ -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);

View File

@@ -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"

View File

@@ -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"

View File

@@ -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>

View File

@@ -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

View File

@@ -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
@@ -782,6 +795,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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -516,6 +516,21 @@ public class CommandQueueTest extends SysuiTestCase {
verify(mCallbacks).setNavigationBarLumaSamplingEnabled(eq(1), eq(true));
}
@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;

View File

@@ -139,6 +139,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.
*

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);