diff options
11 files changed, 814 insertions, 46 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java index 4f33a71b80d5..e326e39a799f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java @@ -16,11 +16,12 @@ package com.android.wm.shell.compatui; +import android.annotation.NonNull; +import android.app.TaskInfo; import android.content.Context; +import android.content.SharedPreferences; import android.provider.DeviceConfig; -import androidx.annotation.NonNull; - import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellMainThread; @@ -34,11 +35,23 @@ import javax.inject.Inject; @WMSingleton public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener { - static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog"; + private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = + "enable_letterbox_restart_dialog"; - static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = + private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = "enable_letterbox_reachability_education"; + /** + * The name of the {@link SharedPreferences} that holds which user has seen the Restart + * confirmation dialog. + */ + private static final String DONT_SHOW_RESTART_DIALOG_PREF_NAME = "dont_show_restart_dialog"; + + /** + * The {@link SharedPreferences} instance for {@link #DONT_SHOW_RESTART_DIALOG_PREF_NAME}. + */ + private final SharedPreferences mSharedPreferences; + // Whether the extended restart dialog is enabled private boolean mIsRestartDialogEnabled; @@ -70,6 +83,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi false); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor, this); + mSharedPreferences = context.getSharedPreferences(DONT_SHOW_RESTART_DIALOG_PREF_NAME, + Context.MODE_PRIVATE); } /** @@ -102,6 +117,20 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi mIsReachabilityEducationOverrideEnabled = enabled; } + boolean getDontShowRestartDialogAgain(TaskInfo taskInfo) { + final int userId = taskInfo.userId; + final String packageName = taskInfo.topActivity.getPackageName(); + return mSharedPreferences.getBoolean( + getDontShowAgainRestartKey(userId, packageName), /* default= */ false); + } + + void setDontShowRestartDialogAgain(TaskInfo taskInfo) { + final int userId = taskInfo.userId; + final String packageName = taskInfo.topActivity.getPackageName(); + mSharedPreferences.edit().putBoolean(getDontShowAgainRestartKey(userId, packageName), + true).apply(); + } + @Override public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { // TODO(b/263349751): Update flag and default value to true @@ -116,4 +145,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false); } } -} + + private String getDontShowAgainRestartKey(int userId, String packageName) { + return packageName + "@" + userId; + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 6627de58cce3..3b2db5127316 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -24,6 +24,7 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.view.Display; import android.view.InsetsSourceControl; @@ -49,6 +50,7 @@ import com.android.wm.shell.transition.Transitions; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -91,6 +93,18 @@ public class CompatUIController implements OnDisplaysChangedListener, private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0); /** + * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are + * currently visible + */ + private final SparseArray<RestartDialogWindowManager> mTaskIdToRestartDialogWindowManagerMap = + new SparseArray<>(0); + + /** + * {@link Set} of task ids for which we need to display a restart confirmation dialog + */ + private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>(); + + /** * The active Letterbox Education layout if there is one (there can be at most one active). * * <p>An active layout is a layout that is eligible to be shown for the associated task but @@ -111,12 +125,15 @@ public class CompatUIController implements OnDisplaysChangedListener, private final ShellExecutor mMainExecutor; private final Lazy<Transitions> mTransitionsLazy; private final DockStateReader mDockStateReader; + private final CompatUIConfiguration mCompatUIConfiguration; private CompatUICallback mCallback; // Only show each hint once automatically in the process life. private final CompatUIHintsState mCompatUIHintsState; + private final CompatUIShellCommandHandler mCompatUIShellCommandHandler; + // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't // be shown. private boolean mKeyguardShowing; @@ -130,7 +147,9 @@ public class CompatUIController implements OnDisplaysChangedListener, SyncTransactionQueue syncQueue, ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader) { + DockStateReader dockStateReader, + CompatUIConfiguration compatUIConfiguration, + CompatUIShellCommandHandler compatUIShellCommandHandler) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -140,14 +159,17 @@ public class CompatUIController implements OnDisplaysChangedListener, mMainExecutor = mainExecutor; mTransitionsLazy = transitionsLazy; mCompatUIHintsState = new CompatUIHintsState(); - shellInit.addInitCallback(this::onInit, this); mDockStateReader = dockStateReader; + mCompatUIConfiguration = compatUIConfiguration; + mCompatUIShellCommandHandler = compatUIShellCommandHandler; + shellInit.addInitCallback(this::onInit, this); } private void onInit() { mShellController.addKeyguardChangeListener(this); mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); + mCompatUIShellCommandHandler.onInit(); } /** Sets the callback for UI interactions. */ @@ -164,6 +186,9 @@ public class CompatUIController implements OnDisplaysChangedListener, */ public void onCompatInfoChanged(TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { + if (taskInfo != null && !taskInfo.topActivityInSizeCompat) { + mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); + } if (taskInfo.configuration == null || taskListener == null) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); @@ -172,6 +197,7 @@ public class CompatUIController implements OnDisplaysChangedListener, createOrUpdateCompatLayout(taskInfo, taskListener); createOrUpdateLetterboxEduLayout(taskInfo, taskListener); + createOrUpdateRestartDialogLayout(taskInfo, taskListener); } @Override @@ -278,7 +304,21 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { return new CompatUIWindowManager(context, taskInfo, mSyncQueue, mCallback, taskListener, - mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState); + mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, + mCompatUIConfiguration, this::onRestartButtonClicked); + } + + private void onRestartButtonClicked( + Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) { + if (mCompatUIConfiguration.isRestartDialogEnabled() + && !mCompatUIConfiguration.getDontShowRestartDialogAgain( + taskInfoState.first)) { + // We need to show the dialog + mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId); + onCompatInfoChanged(taskInfoState.first, taskInfoState.second); + } else { + mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); + } } private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, @@ -327,6 +367,60 @@ public class CompatUIController implements OnDisplaysChangedListener, mActiveLetterboxEduLayout = null; } + private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo, + ShellTaskOrganizer.TaskListener taskListener) { + RestartDialogWindowManager layout = + mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); + if (layout != null) { + // TODO(b/266262111) Handle theme change when taskListener changes + if (layout.getTaskListener() != taskListener) { + mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); + } + layout.setRequestRestartDialog( + mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId)); + // UI already exists, update the UI layout. + if (!layout.updateCompatInfo(taskInfo, taskListener, + showOnDisplay(layout.getDisplayId()))) { + // The layout is no longer eligible to be shown, remove from active layouts. + mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId); + } + return; + } + // Create a new UI layout. + final Context context = getOrCreateDisplayContext(taskInfo.displayId); + if (context == null) { + return; + } + layout = createRestartDialogWindowManager(context, taskInfo, taskListener); + layout.setRequestRestartDialog( + mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId)); + if (layout.createLayout(showOnDisplay(taskInfo.displayId))) { + // The new layout is eligible to be shown, add it the active layouts. + mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout); + } + } + + @VisibleForTesting + RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo, + ShellTaskOrganizer.TaskListener taskListener) { + return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener, + mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(), + this::onRestartDialogCallback, this::onRestartDialogDismissCallback, + mCompatUIConfiguration); + } + + private void onRestartDialogCallback( + Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { + mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId); + mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); + } + + private void onRestartDialogDismissCallback( + Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { + mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId); + onCompatInfoChanged(stateInfo.first, stateInfo.second); + } + private void removeLayouts(int taskId) { final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId); if (layout != null) { @@ -338,6 +432,14 @@ public class CompatUIController implements OnDisplaysChangedListener, mActiveLetterboxEduLayout.release(); mActiveLetterboxEduLayout = null; } + + final RestartDialogWindowManager restartLayout = + mTaskIdToRestartDialogWindowManagerMap.get(taskId); + if (restartLayout != null) { + restartLayout.release(); + mTaskIdToRestartDialogWindowManagerMap.remove(taskId); + mSetOfTaskIdsShowingRestartDialog.remove(taskId); + } } private Context getOrCreateDisplayContext(int displayId) { @@ -382,6 +484,14 @@ public class CompatUIController implements OnDisplaysChangedListener, if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) { callback.accept(mActiveLetterboxEduLayout); } + for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) { + final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i); + final RestartDialogWindowManager layout = + mTaskIdToRestartDialogWindowManagerMap.get(taskId); + if (layout != null && condition.test(layout)) { + callback.accept(layout); + } + } } /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index bce3ec4128e8..c14704d04e3a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -21,12 +21,14 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.content.Context; import android.graphics.Rect; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -38,6 +40,8 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; +import java.util.function.Consumer; + /** * Window manager for the Size Compat restart button and Camera Compat control. */ @@ -50,6 +54,13 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { private final CompatUICallback mCallback; + private final CompatUIConfiguration mCompatUIConfiguration; + + private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; + + @NonNull + private TaskInfo mTaskInfo; + // Remember the last reported states in case visibility changes due to keyguard or IME updates. @VisibleForTesting boolean mHasSizeCompat; @@ -68,12 +79,16 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { CompatUIWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, CompatUICallback callback, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - CompatUIHintsState compatUIHintsState) { + CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) { super(context, taskInfo, syncQueue, taskListener, displayLayout); + mTaskInfo = taskInfo; mCallback = callback; mHasSizeCompat = taskInfo.topActivityInSizeCompat; mCameraCompatControlState = taskInfo.cameraCompatControlState; mCompatUIHintsState = compatUIHintsState; + mCompatUIConfiguration = compatUIConfiguration; + mOnRestartButtonClicked = onRestartButtonClicked; } @Override @@ -119,6 +134,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @Override public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { + mTaskInfo = taskInfo; final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; mHasSizeCompat = taskInfo.topActivityInSizeCompat; @@ -138,7 +154,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { /** Called when the restart button is clicked. */ void onRestartButtonClicked() { - mCallback.onSizeCompatRestartButtonClicked(mTaskId); + mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener())); } /** Called when the camera treatment button is clicked. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java index face24340a4e..db87f657c1b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -151,6 +151,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana @Override public void setConfiguration(Configuration configuration) { super.setConfiguration(configuration); + // TODO(b/266262111): Investigate loss of theme configuration when switching TaskListener mContext = mContext.createConfigurationContext(configuration); } @@ -169,6 +170,10 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana initSurface(mLeash); } + protected ShellTaskOrganizer.TaskListener getTaskListener() { + return mTaskListener; + } + /** Inits the z-order of the surface. */ private void initSurface(SurfaceControl leash) { final int z = getZOrder(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java new file mode 100644 index 000000000000..c53e6389331a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 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.wm.shell.compatui; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.wm.shell.R; + +import java.util.function.Consumer; + +/** + * Container for a SCM restart confirmation dialog and background dim. + */ +public class RestartDialogLayout extends ConstraintLayout implements DialogContainerSupplier { + + private View mDialogContainer; + private TextView mDialogTitle; + private Drawable mBackgroundDim; + + public RestartDialogLayout(Context context) { + this(context, null); + } + + public RestartDialogLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public View getDialogContainerView() { + return mDialogContainer; + } + + TextView getDialogTitle() { + return mDialogTitle; + } + + @Override + public Drawable getBackgroundDimDrawable() { + return mBackgroundDim; + } + + /** + * Register a callback for the dismiss button and background dim. + * + * @param callback The callback to register or null if all on click listeners should be removed. + */ + void setDismissOnClickListener(@Nullable Runnable callback) { + final OnClickListener listener = callback == null ? null : view -> callback.run(); + findViewById(R.id.letterbox_restart_dialog_dismiss_button).setOnClickListener(listener); + } + + /** + * Register a callback for the restart button + * + * @param callback The callback to register or null if all on click listeners should be removed. + */ + void setRestartOnClickListener(@Nullable Consumer<Boolean> callback) { + final CheckBox dontShowAgainCheckbox = findViewById(R.id.letterbox_restart_dialog_checkbox); + final OnClickListener listener = callback == null ? null : view -> callback.accept( + dontShowAgainCheckbox.isChecked()); + findViewById(R.id.letterbox_restart_dialog_restart_button).setOnClickListener(listener); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mDialogContainer = findViewById(R.id.letterbox_restart_dialog_container); + mDialogTitle = findViewById(R.id.letterbox_restart_dialog_title); + mBackgroundDim = getBackground().mutate(); + // Set the alpha of the background dim to 0 for enter animation. + mBackgroundDim.setAlpha(0); + // We add a no-op on-click listener to the dialog container so that clicks on it won't + // propagate to the listener of the layout (which represents the background dim). + mDialogContainer.setOnClickListener(view -> {}); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java new file mode 100644 index 000000000000..10f25d0eef11 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2022 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.wm.shell.compatui; + +import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Rect; +import android.provider.Settings; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.transition.Transitions; + +import java.util.function.Consumer; + +/** + * Window manager for the Restart Dialog. + * + * TODO(b/263484314): Create abstraction of RestartDialogWindowManager and LetterboxEduWindowManager + */ +class RestartDialogWindowManager extends CompatUIWindowManagerAbstract { + + /** + * The restart dialog should be the topmost child of the Task in case there can be more + * than one child. + */ + private static final int Z_ORDER = Integer.MAX_VALUE; + + private final DialogAnimationController<RestartDialogLayout> mAnimationController; + + private final Transitions mTransitions; + + // Remember the last reported state in case visibility changes due to keyguard or IME updates. + private boolean mRequestRestartDialog; + + private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback; + + private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback; + + private final CompatUIConfiguration mCompatUIConfiguration; + + /** + * The vertical margin between the dialog container and the task stable bounds (excluding + * insets). + */ + private final int mDialogVerticalMargin; + + @NonNull + private TaskInfo mTaskInfo; + + @Nullable + @VisibleForTesting + RestartDialogLayout mLayout; + + RestartDialogWindowManager(Context context, TaskInfo taskInfo, + SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, + DisplayLayout displayLayout, Transitions transitions, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback, + CompatUIConfiguration compatUIConfiguration) { + this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions, + onRestartCallback, onDismissCallback, + new DialogAnimationController<>(context, "RestartDialogWindowManager"), + compatUIConfiguration); + } + + @VisibleForTesting + RestartDialogWindowManager(Context context, TaskInfo taskInfo, + SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, + DisplayLayout displayLayout, Transitions transitions, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback, + Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback, + DialogAnimationController<RestartDialogLayout> animationController, + CompatUIConfiguration compatUIConfiguration) { + super(context, taskInfo, syncQueue, taskListener, displayLayout); + mTaskInfo = taskInfo; + mTransitions = transitions; + mOnDismissCallback = onDismissCallback; + mOnRestartCallback = onRestartCallback; + mAnimationController = animationController; + mDialogVerticalMargin = (int) mContext.getResources().getDimension( + R.dimen.letterbox_restart_dialog_margin); + mCompatUIConfiguration = compatUIConfiguration; + } + + @Override + protected int getZOrder() { + return Z_ORDER; + } + + @Override + @Nullable + protected View getLayout() { + return mLayout; + } + + @Override + protected void removeLayout() { + mLayout = null; + } + + @Override + protected boolean eligibleToShowLayout() { + // We don't show this dialog if the user has explicitly selected so clicking on a checkbox. + return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null + || !mCompatUIConfiguration.getDontShowRestartDialogAgain(mTaskInfo)); + } + + @Override + protected View createLayout() { + mLayout = inflateLayout(); + updateDialogMargins(); + + // startEnterAnimation will be called immediately if shell-transitions are disabled. + mTransitions.runOnIdle(this::startEnterAnimation); + + return mLayout; + } + + void setRequestRestartDialog(boolean enabled) { + mRequestRestartDialog = enabled; + } + + @Override + public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, + boolean canShow) { + mTaskInfo = taskInfo; + return super.updateCompatInfo(taskInfo, taskListener, canShow); + } + + private void updateDialogMargins() { + if (mLayout == null) { + return; + } + final View dialogContainer = mLayout.getDialogContainerView(); + ViewGroup.MarginLayoutParams marginParams = + (ViewGroup.MarginLayoutParams) dialogContainer.getLayoutParams(); + + final Rect taskBounds = getTaskBounds(); + final Rect taskStableBounds = getTaskStableBounds(); + + marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin; + marginParams.bottomMargin = + taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin; + dialogContainer.setLayoutParams(marginParams); + } + + private RestartDialogLayout inflateLayout() { + return (RestartDialogLayout) LayoutInflater.from(mContext).inflate( + R.layout.letterbox_restart_dialog_layout, null); + } + + private void startEnterAnimation() { + if (mLayout == null) { + // Dialog has already been released. + return; + } + mAnimationController.startEnterAnimation(mLayout, /* endCallback= */ + this::onDialogEnterAnimationEnded); + } + + private void onDialogEnterAnimationEnded() { + if (mLayout == null) { + // Dialog has already been released. + return; + } + mLayout.setDismissOnClickListener(this::onDismiss); + mLayout.setRestartOnClickListener(dontShowAgain -> { + if (mLayout != null) { + mLayout.setDismissOnClickListener(null); + mAnimationController.startExitAnimation(mLayout, () -> { + release(); + }); + } + if (dontShowAgain) { + mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo); + } + mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + }); + // Focus on the dialog title for accessibility. + mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + + private void onDismiss() { + if (mLayout == null) { + return; + } + + mLayout.setDismissOnClickListener(null); + mAnimationController.startExitAnimation(mLayout, () -> { + release(); + mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener())); + }); + } + + @Override + public void release() { + mAnimationController.cancelAnimation(); + super.release(); + } + + @Override + protected void onParentBoundsChanged() { + if (mLayout == null) { + return; + } + // Both the layout dimensions and dialog margins depend on the parent bounds. + WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams(); + mLayout.setLayoutParams(windowLayoutParams); + updateDialogMargins(); + relayout(windowLayoutParams); + } + + @Override + protected void updateSurfacePosition() { + // Nothing to do, since the position of the surface is fixed to the top left corner (0,0) + // of the task (parent surface), which is the default position of a surface. + } + + @Override + protected WindowManager.LayoutParams getWindowLayoutParams() { + final Rect taskBounds = getTaskBounds(); + return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */ + taskBounds.height()); + } + + @VisibleForTesting + boolean isTaskbarEduShowing() { + return Settings.Secure.getInt(mContext.getContentResolver(), + LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 09f5cf1d31e4..25c430c27457 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -56,7 +56,9 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.compatui.CompatUIShellCommandHandler; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; @@ -196,10 +198,11 @@ public abstract class WMShellBaseModule { DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader) { + DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration, + CompatUIShellCommandHandler compatUIShellCommandHandler) { return new CompatUIController(context, shellInit, shellController, displayController, displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy, - dockStateReader); + dockStateReader, compatUIConfiguration, compatUIShellCommandHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 2fc0914acbd4..875e6105b7bc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -94,7 +94,10 @@ public class CompatUIControllerTest extends ShellTestCase { private @Mock Lazy<Transitions> mMockTransitionsLazy; private @Mock CompatUIWindowManager mMockCompatLayout; private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout; + private @Mock RestartDialogWindowManager mMockRestartDialogLayout; private @Mock DockStateReader mDockStateReader; + private @Mock CompatUIConfiguration mCompatUIConfiguration; + private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -112,10 +115,17 @@ public class CompatUIControllerTest extends ShellTestCase { doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId(); doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); + + doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId(); + doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId(); + doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean()); + doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean()); + mShellInit = spy(new ShellInit(mMockExecutor)); mController = new CompatUIController(mContext, mShellInit, mMockShellController, mMockDisplayController, mMockDisplayInsetsController, mMockImeController, - mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) { + mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, + mCompatUIConfiguration, mCompatUIShellCommandHandler) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { @@ -127,6 +137,12 @@ public class CompatUIControllerTest extends ShellTestCase { TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { return mMockLetterboxEduLayout; } + + @Override + RestartDialogWindowManager createRestartDialogWindowManager(Context context, + TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { + return mMockRestartDialogLayout; + } }; mShellInit.init(); spyOn(mController); @@ -159,6 +175,8 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); // Verify that the compat controls and letterbox education are updated with new size compat // info. @@ -167,10 +185,12 @@ public class CompatUIControllerTest extends ShellTestCase { CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); // Verify that compat controls and letterbox education are removed with null task listener. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); @@ -180,12 +200,14 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).release(); verify(mMockLetterboxEduLayout).release(); + verify(mMockRestartDialogLayout).release(); } @Test public void testOnCompatInfoChanged_createLayoutReturnsFalse() { doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean()); doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); + doReturn(false).when(mMockRestartDialogLayout).createLayout(anyBoolean()); TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); @@ -194,6 +216,8 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); // Verify that the layout is created again. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); @@ -201,15 +225,19 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); + verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); } @Test public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() { doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean()); doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); + doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean()); TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); @@ -218,24 +246,33 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout, + mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - true); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ true); // Verify that the layout is created again. - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout, + mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); + verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); } @@ -259,6 +296,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, never()).release(); verify(mMockLetterboxEduLayout, never()).release(); + verify(mMockRestartDialogLayout, never()).release(); verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID), any()); @@ -267,6 +305,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any()); verify(mMockCompatLayout).release(); verify(mMockLetterboxEduLayout).release(); + verify(mMockRestartDialogLayout).release(); } @Test @@ -278,11 +317,13 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, never()).updateDisplayLayout(any()); verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any()); + verify(mMockRestartDialogLayout, never()).updateDisplayLayout(any()); mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration()); verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout); + verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout); } @Test @@ -301,12 +342,14 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout); + verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout); // No update if the insets state is the same. - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout); mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState)); verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout); + verify(mMockRestartDialogLayout, never()).updateDisplayLayout(mMockDisplayLayout); } @Test @@ -319,22 +362,26 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button remains hidden while IME is showing. TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); // Verify button is shown after IME is hidden. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } @Test @@ -347,22 +394,26 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button remains hidden while keyguard is showing. TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); - verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ - false); + verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); + verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener, + /* canShow= */ false); // Verify button is shown after keyguard becomes not showing. mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } @Test @@ -375,20 +426,23 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); + verify(mMockRestartDialogLayout, times(2)).updateVisibility(false); - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout); // Verify button remains hidden after keyguard becomes not showing since IME is showing. mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button is shown after IME is not showing. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } @Test @@ -401,20 +455,23 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); + verify(mMockRestartDialogLayout, times(2)).updateVisibility(false); - clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout); // Verify button remains hidden after IME is hidden since keyguard is showing. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); verify(mMockCompatLayout).updateVisibility(false); verify(mMockLetterboxEduLayout).updateVisibility(false); + verify(mMockRestartDialogLayout).updateVisibility(false); // Verify button is shown after keyguard becomes not showing. mController.onKeyguardVisibilityChanged(false, false, false); verify(mMockCompatLayout).updateVisibility(true); verify(mMockLetterboxEduLayout).updateVisibility(true); + verify(mMockRestartDialogLayout).updateVisibility(true); } private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index 7d3e718313e6..5f294d53b662 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -31,6 +31,7 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.testing.AndroidTestingRunner; +import android.util.Pair; import android.view.LayoutInflater; import android.view.SurfaceControlViewHost; import android.widget.ImageButton; @@ -45,12 +46,17 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.Consumer; + /** * Tests for {@link CompatUILayout}. * @@ -65,20 +71,22 @@ public class CompatUILayoutTest extends ShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private SurfaceControlViewHost mViewHost; + @Mock private CompatUIConfiguration mCompatUIConfiguration; private CompatUIWindowManager mWindowManager; private CompatUILayout mLayout; + private TaskInfo mTaskInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mWindowManager = new CompatUIWindowManager(mContext, - createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN), - mSyncTransactionQueue, mCallback, mTaskListener, - new DisplayLayout(), new CompatUIHintsState()); + mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, + mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCompatUIConfiguration, mOnRestartButtonClicked); mLayout = (CompatUILayout) LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null); @@ -95,8 +103,15 @@ public class CompatUILayoutTest extends ShellTestCase { final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button); button.performClick(); + @SuppressWarnings("unchecked") + ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor = + ArgumentCaptor.forClass(Pair.class); + verify(mWindowManager).onRestartButtonClicked(); - verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); + verify(mOnRestartButtonClicked).accept(restartCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue(); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index e79b803b4304..324940e4113c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -38,6 +38,7 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.graphics.Rect; import android.testing.AndroidTestingRunner; +import android.util.Pair; import android.view.DisplayInfo; import android.view.InsetsSource; import android.view.InsetsState; @@ -53,12 +54,17 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.Consumer; + /** * Tests for {@link CompatUIWindowManager}. * @@ -73,20 +79,22 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private CompatUILayout mLayout; @Mock private SurfaceControlViewHost mViewHost; + @Mock private CompatUIConfiguration mCompatUIConfiguration; private CompatUIWindowManager mWindowManager; + private TaskInfo mTaskInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mWindowManager = new CompatUIWindowManager(mContext, - createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN), - mSyncTransactionQueue, mCallback, mTaskListener, - new DisplayLayout(), new CompatUIHintsState()); + mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, + mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCompatUIConfiguration, mOnRestartButtonClicked); spyOn(mWindowManager); doReturn(mLayout).when(mWindowManager).inflateLayout(); @@ -404,7 +412,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { public void testOnRestartButtonClicked() { mWindowManager.onRestartButtonClicked(); - verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID); + @SuppressWarnings("unchecked") + ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor = + ArgumentCaptor.forClass(Pair.class); + + verify(mOnRestartButtonClicked).accept(restartCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue(); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java new file mode 100644 index 000000000000..e2dcdb0e91b2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 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.wm.shell.compatui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.Consumer; + +/** + * Tests for {@link RestartDialogLayout}. + * + * Build/Install/Run: + * atest WMShellUnitTests:RestartDialogLayoutTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class RestartDialogLayoutTest extends ShellTestCase { + + @Mock private Runnable mDismissCallback; + @Mock private Consumer<Boolean> mRestartCallback; + + private RestartDialogLayout mLayout; + private View mDismissButton; + private View mRestartButton; + private View mDialogContainer; + private CheckBox mDontRepeatCheckBox; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mLayout = (RestartDialogLayout) + LayoutInflater.from(mContext).inflate(R.layout.letterbox_restart_dialog_layout, + null); + mDismissButton = mLayout.findViewById(R.id.letterbox_restart_dialog_dismiss_button); + mRestartButton = mLayout.findViewById(R.id.letterbox_restart_dialog_restart_button); + mDialogContainer = mLayout.findViewById(R.id.letterbox_restart_dialog_container); + mDontRepeatCheckBox = mLayout.findViewById(R.id.letterbox_restart_dialog_checkbox); + mLayout.setDismissOnClickListener(mDismissCallback); + mLayout.setRestartOnClickListener(mRestartCallback); + } + + @Test + public void testOnFinishInflate() { + assertEquals(mLayout.getDialogContainerView(), + mLayout.findViewById(R.id.letterbox_restart_dialog_container)); + assertEquals(mLayout.getDialogTitle(), + mLayout.findViewById(R.id.letterbox_restart_dialog_title)); + assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground()); + assertEquals(mLayout.getBackground().getAlpha(), 0); + } + + @Test + public void testOnDismissButtonClicked() { + assertTrue(mDismissButton.performClick()); + + verify(mDismissCallback).run(); + } + + @Test + public void testOnRestartButtonClickedWithoutCheckbox() { + mDontRepeatCheckBox.setChecked(false); + assertTrue(mRestartButton.performClick()); + + verify(mRestartCallback).accept(false); + } + + @Test + public void testOnRestartButtonClickedWithCheckbox() { + mDontRepeatCheckBox.setChecked(true); + assertTrue(mRestartButton.performClick()); + + verify(mRestartCallback).accept(true); + } + + @Test + public void testOnBackgroundClickedDoesntDismiss() { + assertFalse(mLayout.performClick()); + + verify(mDismissCallback, never()).run(); + } + + @Test + public void testOnDialogContainerClicked() { + assertTrue(mDialogContainer.performClick()); + + verify(mDismissCallback, never()).run(); + verify(mRestartCallback, never()).accept(anyBoolean()); + } + + @Test + public void testSetDismissOnClickListenerNull() { + mLayout.setDismissOnClickListener(null); + + assertFalse(mDismissButton.performClick()); + assertFalse(mLayout.performClick()); + assertTrue(mDialogContainer.performClick()); + + verify(mDismissCallback, never()).run(); + } + + @Test + public void testSetRestartOnClickListenerNull() { + mLayout.setRestartOnClickListener(null); + + assertFalse(mRestartButton.performClick()); + assertFalse(mLayout.performClick()); + assertTrue(mDialogContainer.performClick()); + + verify(mRestartCallback, never()).accept(anyBoolean()); + } + +} |