diff options
9 files changed, 433 insertions, 192 deletions
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml index 347c2b47767e..0dea87c6b7fc 100644 --- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml +++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml @@ -14,35 +14,52 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.wm.shell.sizecompatui.SizeCompatHintPopup + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@android:color/background_light" - android:orientation="vertical"> + android:layout_height="wrap_content"> - <TextView - android:layout_width="180dp" - android:layout_height="wrap_content" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:paddingTop="10dp" - android:text="@string/restart_button_description" - android:textAlignment="viewStart" - android:textColor="@android:color/primary_text_light" - android:textSize="16sp" /> - - <Button - android:id="@+id/got_it" + <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:includeFontPadding="false" - android:layout_gravity="end" - android:minHeight="36dp" - android:background="?android:attr/selectableItemBackground" - android:text="@string/got_it" - android:textAllCaps="true" - android:textColor="#3c78d8" - android:textSize="16sp" - android:textStyle="bold" /> - -</LinearLayout> + android:gravity="center" + android:clipToPadding="false" + android:padding="@dimen/bubble_elevation"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/background_light" + android:elevation="@dimen/bubble_elevation" + android:orientation="vertical"> + + <TextView + android:layout_width="180dp" + android:layout_height="wrap_content" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:paddingTop="10dp" + android:text="@string/restart_button_description" + android:textAlignment="viewStart" + android:textColor="@android:color/primary_text_light" + android:textSize="16sp"/> + + <Button + android:id="@+id/got_it" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:includeFontPadding="false" + android:layout_gravity="end" + android:minHeight="36dp" + android:background="?android:attr/selectableItemBackground" + android:text="@string/got_it" + android:textAllCaps="true" + android:textColor="#3c78d8" + android:textSize="16sp" + android:textStyle="bold"/> + + </LinearLayout> + + </FrameLayout> + +</com.android.wm.shell.sizecompatui.SizeCompatHintPopup> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java new file mode 100644 index 000000000000..78af9df30e6a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 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.sizecompatui; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.RippleDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; + +/** Popup to show the hint about the {@link SizeCompatRestartButton}. */ +public class SizeCompatHintPopup extends FrameLayout implements View.OnClickListener { + + private SizeCompatUILayout mLayout; + + public SizeCompatHintPopup(Context context) { + super(context); + } + + public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public SizeCompatHintPopup(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + void inject(SizeCompatUILayout layout) { + mLayout = layout; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + final Button gotItButton = findViewById(R.id.got_it); + gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY), + null /* content */, null /* mask */)); + gotItButton.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + mLayout.dismissHint(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java index 9094d7de8d63..08a840297df1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java @@ -22,19 +22,13 @@ import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; -import android.view.WindowManager; -import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.PopupWindow; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; /** Button to restart the size compat activity. */ @@ -42,10 +36,6 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick View.OnLongClickListener { private SizeCompatUILayout mLayout; - private ImageButton mRestartButton; - @VisibleForTesting - PopupWindow mShowingHint; - private WindowManager.LayoutParams mWinParams; public SizeCompatRestartButton(@NonNull Context context) { super(context); @@ -67,24 +57,19 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick void inject(SizeCompatUILayout layout) { mLayout = layout; - mWinParams = layout.getWindowLayoutParams(); - } - - void remove() { - dismissHint(); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mRestartButton = findViewById(R.id.size_compat_restart_button); + final ImageButton restartButton = findViewById(R.id.size_compat_restart_button); final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY); final GradientDrawable mask = new GradientDrawable(); mask.setShape(GradientDrawable.OVAL); mask.setColor(color); - mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask)); - mRestartButton.setOnClickListener(this); - mRestartButton.setOnLongClickListener(this); + restartButton.setBackground(new RippleDrawable(color, null /* content */, mask)); + restartButton.setOnClickListener(this); + restartButton.setOnLongClickListener(this); } @Override @@ -94,69 +79,7 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick @Override public boolean onLongClick(View v) { - showHint(); + mLayout.onRestartButtonLongClicked(); return true; } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mLayout.mShouldShowHint) { - mLayout.mShouldShowHint = false; - showHint(); - } - } - - @Override - public void setVisibility(@Visibility int visibility) { - if (visibility == View.GONE && mShowingHint != null) { - // Also dismiss the popup. - dismissHint(); - } - super.setVisibility(visibility); - } - - @Override - public void setLayoutDirection(int layoutDirection) { - final int gravity = SizeCompatUILayout.getGravity(layoutDirection); - if (mWinParams.gravity != gravity) { - mWinParams.gravity = gravity; - getContext().getSystemService(WindowManager.class).updateViewLayout(this, - mWinParams); - } - super.setLayoutDirection(layoutDirection); - } - - void showHint() { - if (mShowingHint != null) { - return; - } - - // TODO: popup is not attached to the button surface. Need to handle this differently for - // non-fullscreen task. - final View popupView = LayoutInflater.from(getContext()).inflate( - R.layout.size_compat_mode_hint, null); - final PopupWindow popupWindow = new PopupWindow(popupView, - LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); - popupWindow.setWindowLayoutType(mWinParams.type); - popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation)); - popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod); - popupWindow.setClippingEnabled(false); - popupWindow.setOnDismissListener(() -> mShowingHint = null); - mShowingHint = popupWindow; - - final Button gotItButton = popupView.findViewById(R.id.got_it); - gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY), - null /* content */, null /* mask */)); - gotItButton.setOnClickListener(view -> dismissHint()); - popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX, - mLayout.mPopupOffsetY); - } - - void dismissHint() { - if (mShowingHint != null) { - mShowingHint.dismiss(); - mShowingHint = null; - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java index a3880f497ff3..c981adee9b5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java @@ -50,7 +50,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang /** Whether the IME is shown on display id. */ private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); - /** The showing buttons by task id. */ + /** The showing UIs by task id. */ private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0); /** Avoid creating display context frequently for non-default display. */ @@ -77,12 +77,12 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } /** - * Called when the Task info changed. Creates and updates the restart button if there is an - * activity in size compat, or removes the restart button if there is no size compat activity. + * Called when the Task info changed. Creates and updates the size compat UI if there is an + * activity in size compat, or removes the UI if there is no size compat activity. * * @param displayId display the task and activity are in. * @param taskId task the activity is in. - * @param taskConfig task config to place the restart button with. + * @param taskConfig task config to place the size compat UI with. * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the * top activity in this Task is not in size compat. * @param taskListener listener to handle the Task Surface placement. @@ -94,10 +94,10 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang // Null token means the current foreground activity is not in size compatibility mode. removeLayout(taskId); } else if (mActiveLayouts.contains(taskId)) { - // Button already exists, update the button layout. + // UI already exists, update the UI layout. updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener); } else { - // Create a new restart button. + // Create a new size compat UI. createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener); } } @@ -106,7 +106,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang public void onDisplayRemoved(int displayId) { mDisplayContextCache.remove(displayId); - // Remove all buttons on the removed display. + // Remove all size compat UIs on the removed display. final List<Integer> toRemoveTaskIds = new ArrayList<>(); forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId())); for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) { @@ -128,7 +128,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang mDisplaysWithIme.remove(displayId); } - // Hide the button when input method is showing. + // Hide the size compat UIs when input method is showing. forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java index 5924b53f822c..32f3648be19a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java @@ -30,7 +30,6 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; -import android.view.Gravity; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; @@ -43,7 +42,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; /** * Records and handles layout of size compat UI on a task with size compat activity. Helps to - * calculate proper bounds when configuration or button position changes. + * calculate proper bounds when configuration or UI position changes. */ class SizeCompatUILayout { private static final String TAG = "SizeCompatUILayout"; @@ -56,12 +55,18 @@ class SizeCompatUILayout { private IBinder mActivityToken; private ShellTaskOrganizer.TaskListener mTaskListener; private DisplayLayout mDisplayLayout; - @VisibleForTesting - final SizeCompatUIWindowManager mWindowManager; @VisibleForTesting + final SizeCompatUIWindowManager mButtonWindowManager; + @VisibleForTesting + @Nullable + SizeCompatUIWindowManager mHintWindowManager; + @VisibleForTesting @Nullable SizeCompatRestartButton mButton; + @VisibleForTesting + @Nullable + SizeCompatHintPopup mHint; final int mButtonSize; final int mPopupOffsetX; final int mPopupOffsetY; @@ -79,7 +84,7 @@ class SizeCompatUILayout { mTaskListener = taskListener; mDisplayLayout = displayLayout; mShouldShowHint = !hasShownHint; - mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this); + mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this); mButtonSize = mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size); @@ -87,21 +92,52 @@ class SizeCompatUILayout { mPopupOffsetY = mButtonSize; } - /** Creates the button window. */ + /** Creates the activity restart button window. */ void createSizeCompatButton(boolean isImeShowing) { if (isImeShowing || mButton != null) { // When ime is showing, wait until ime is dismiss to create UI. return; } - mButton = mWindowManager.createSizeCompatUI(); - updateSurfacePosition(); + mButton = mButtonWindowManager.createSizeCompatButton(); + updateButtonSurfacePosition(); + + if (mShouldShowHint) { + // Only show by default for the first time. + mShouldShowHint = false; + createSizeCompatHint(); + } + } + + /** Creates the restart button hint window. */ + private void createSizeCompatHint() { + if (mHint != null) { + // Hint already shown. + return; + } + mHintWindowManager = createHintWindowManager(); + mHint = mHintWindowManager.createSizeCompatHint(); + updateHintSurfacePosition(); } - /** Releases the button window. */ + @VisibleForTesting + SizeCompatUIWindowManager createHintWindowManager() { + return new SizeCompatUIWindowManager(mContext, mTaskConfig, this); + } + + /** Dismisses the hint window. */ + void dismissHint() { + mHint = null; + if (mHintWindowManager != null) { + mHintWindowManager.release(); + mHintWindowManager = null; + } + } + + /** Releases the UI windows. */ void release() { - mButton.remove(); + dismissHint(); mButton = null; - mWindowManager.release(); + mButtonWindowManager.release(); } /** Called when size compat info changed. */ @@ -115,7 +151,10 @@ class SizeCompatUILayout { // Update configuration. mContext = mContext.createConfigurationContext(taskConfig); - mWindowManager.setConfiguration(taskConfig); + mButtonWindowManager.setConfiguration(taskConfig); + if (mHintWindowManager != null) { + mHintWindowManager.setConfiguration(taskConfig); + } if (mButton == null || prevTaskListener != taskListener) { // TaskListener changed, recreate the button for new surface parent. @@ -126,14 +165,19 @@ class SizeCompatUILayout { if (!taskConfig.windowConfiguration.getBounds() .equals(prevTaskConfig.windowConfiguration.getBounds())) { - // Reposition the button surface. - updateSurfacePosition(); + // Reposition the UI surfaces. + updateButtonSurfacePosition(); + updateHintSurfacePosition(); } if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) { // Update layout for RTL. mButton.setLayoutDirection(taskConfig.getLayoutDirection()); - updateSurfacePosition(); + updateButtonSurfacePosition(); + if (mHint != null) { + mHint.setLayoutDirection(taskConfig.getLayoutDirection()); + updateHintSurfacePosition(); + } } } @@ -149,8 +193,9 @@ class SizeCompatUILayout { displayLayout.getStableBounds(curStableBounds); mDisplayLayout = displayLayout; if (!prevStableBounds.equals(curStableBounds)) { - // Stable bounds changed, update button surface position. - updateSurfacePosition(); + // Stable bounds changed, update UI surface positions. + updateButtonSurfacePosition(); + updateHintSurfacePosition(); } } @@ -162,27 +207,46 @@ class SizeCompatUILayout { return; } + // Hide size compat UIs when IME is showing. final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE; if (mButton.getVisibility() != newVisibility) { mButton.setVisibility(newVisibility); } + if (mHint != null && mHint.getVisibility() != newVisibility) { + mHint.setVisibility(newVisibility); + } } /** Gets the layout params for restart button. */ - WindowManager.LayoutParams getWindowLayoutParams() { + WindowManager.LayoutParams getButtonWindowLayoutParams() { final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( + // Cannot be wrap_content as this determines the actual window size mButtonSize, mButtonSize, TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT); - winParams.gravity = getGravity(getLayoutDirection()); winParams.token = new Binder(); - winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId()); + winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + getTaskId()); winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; return winParams; } - /** Called when it is ready to be placed button surface button. */ + /** Gets the layout params for hint popup. */ + WindowManager.LayoutParams getHintWindowLayoutParams(SizeCompatHintPopup hint) { + final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( + // Cannot be wrap_content as this determines the actual window size + hint.getMeasuredWidth(), hint.getMeasuredHeight(), + TYPE_APPLICATION_OVERLAY, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT); + winParams.token = new Binder(); + winParams.setTitle(SizeCompatHintPopup.class.getSimpleName() + getTaskId()); + winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + winParams.windowAnimations = android.R.style.Animation_InputMethod; + return winParams; + } + + /** Called when it is ready to be placed size compat UI surface. */ void attachToParentSurface(SurfaceControl.Builder b) { mTaskListener.attachChildSurfaceToTask(mTaskId, b); } @@ -192,13 +256,17 @@ class SizeCompatUILayout { ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken); } + /** Called when the restart button is long clicked. */ + void onRestartButtonLongClicked() { + createSizeCompatHint(); + } + @VisibleForTesting - void updateSurfacePosition() { - if (mButton == null || mWindowManager.getSurfaceControl() == null) { + void updateButtonSurfacePosition() { + if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) { return; } - // The hint popup won't be at the correct position. - mButton.dismissHint(); + final SurfaceControl leash = mButtonWindowManager.getSurfaceControl(); // Use stable bounds to prevent the button from overlapping with system bars. final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); @@ -212,8 +280,30 @@ class SizeCompatUILayout { : stableBounds.right - taskBounds.left - mButtonSize; final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize; - mSyncQueue.runInSync(t -> - t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY)); + mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY)); + } + + void updateHintSurfacePosition() { + if (mHint == null || mHintWindowManager == null + || mHintWindowManager.getSurfaceControl() == null) { + return; + } + final SurfaceControl leash = mHintWindowManager.getSurfaceControl(); + + // Use stable bounds to prevent the hint from overlapping with system bars. + final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); + final Rect stableBounds = new Rect(); + mDisplayLayout.getStableBounds(stableBounds); + stableBounds.intersect(taskBounds); + + // Position of the hint in the container coordinate. + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? stableBounds.left - taskBounds.left + mPopupOffsetX + : stableBounds.right - taskBounds.left - mPopupOffsetX - mHint.getMeasuredWidth(); + final int positionY = + stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight(); + + mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY)); } int getDisplayId() { @@ -227,9 +317,4 @@ class SizeCompatUILayout { private int getLayoutDirection() { return mContext.getResources().getConfiguration().getLayoutDirection(); } - - static int getGravity(int layoutDirection) { - return Gravity.BOTTOM - | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java index a7ad982a4736..f634c4586e39 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java @@ -24,12 +24,14 @@ import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.SurfaceSession; +import android.view.View; import android.view.WindowlessWindowManager; import com.android.wm.shell.R; /** - * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton}. + * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton} or + * {@link SizeCompatHintPopup}. */ class SizeCompatUIWindowManager extends WindowlessWindowManager { @@ -67,18 +69,39 @@ class SizeCompatUIWindowManager extends WindowlessWindowManager { } /** Inflates {@link SizeCompatRestartButton} on to the root surface. */ - SizeCompatRestartButton createSizeCompatUI() { - if (mViewHost == null) { - mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + SizeCompatRestartButton createSizeCompatButton() { + if (mViewHost != null) { + throw new IllegalStateException( + "A UI has already been created with this window manager."); } + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + final SizeCompatRestartButton button = (SizeCompatRestartButton) LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null); button.inject(mLayout); - mViewHost.setView(button, mLayout.getWindowLayoutParams()); + mViewHost.setView(button, mLayout.getButtonWindowLayoutParams()); return button; } + /** Inflates {@link SizeCompatHintPopup} on to the root surface. */ + SizeCompatHintPopup createSizeCompatHint() { + if (mViewHost != null) { + throw new IllegalStateException( + "A UI has already been created with this window manager."); + } + + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + + final SizeCompatHintPopup hint = (SizeCompatHintPopup) + LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null); + // Measure how big the hint is. + hint.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + hint.inject(mLayout); + mViewHost.setView(hint, mLayout.getHintWindowLayoutParams(hint)); + return hint; + } + /** Releases the surface control and tears down the view hierarchy. */ void release() { if (mViewHost != null) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java new file mode 100644 index 000000000000..9845d4650d20 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 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.sizecompatui; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; +import android.widget.Button; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link SizeCompatHintPopup}. + * + * Build/Install/Run: + * atest WMShellUnitTests:SizeCompatHintPopupTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SizeCompatHintPopupTest extends ShellTestCase { + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock private IBinder mActivityToken; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private DisplayLayout mDisplayLayout; + + private SizeCompatUILayout mLayout; + private SizeCompatHintPopup mHint; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final int taskId = 1; + mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(), + taskId, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/); + mHint = (SizeCompatHintPopup) + LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null); + mHint.inject(mLayout); + + spyOn(mLayout); + } + + @Test + public void testOnClick() { + doNothing().when(mLayout).dismissHint(); + + final Button button = mHint.findViewById(R.id.got_it); + button.performClick(); + + verify(mLayout).dismissHint(); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java index d9086a6ccdc1..5a43925a5677 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java @@ -19,13 +19,13 @@ package com.android.wm.shell.sizecompatui; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.os.IBinder; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; +import android.widget.ImageButton; import androidx.test.filters.SmallTest; @@ -71,45 +71,25 @@ public class SizeCompatRestartButtonTest extends ShellTestCase { mButton.inject(mLayout); spyOn(mLayout); - spyOn(mButton); - doNothing().when(mButton).showHint(); } @Test public void testOnClick() { doNothing().when(mLayout).onRestartButtonClicked(); - mButton.onClick(mButton); + final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button); + button.performClick(); verify(mLayout).onRestartButtonClicked(); } @Test public void testOnLongClick() { - verify(mButton, never()).showHint(); + doNothing().when(mLayout).onRestartButtonLongClicked(); - mButton.onLongClick(mButton); + final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button); + button.performLongClick(); - verify(mButton).showHint(); - } - - @Test - public void testOnAttachedToWindow_showHint() { - mLayout.mShouldShowHint = false; - mButton.onAttachedToWindow(); - - verify(mButton, never()).showHint(); - - mLayout.mShouldShowHint = true; - mButton.onAttachedToWindow(); - - verify(mButton).showHint(); - } - - @Test - public void testRemove() { - mButton.remove(); - - verify(mButton).dismissHint(); + verify(mLayout).onRestartButtonLongClicked(); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java index 236db44bdce0..f33cfe86224f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java @@ -18,6 +18,7 @@ package com.android.wm.shell.sizecompatui; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -27,6 +28,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ActivityClient; @@ -68,6 +70,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase { @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private DisplayLayout mDisplayLayout; @Mock private SizeCompatRestartButton mButton; + @Mock private SizeCompatHintPopup mHint; private Configuration mTaskConfig; private SizeCompatUILayout mLayout; @@ -81,8 +84,13 @@ public class SizeCompatUILayoutTest extends ShellTestCase { TASK_ID, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/); spyOn(mLayout); - spyOn(mLayout.mWindowManager); - doReturn(mButton).when(mLayout.mWindowManager).createSizeCompatUI(); + spyOn(mLayout.mButtonWindowManager); + doReturn(mButton).when(mLayout.mButtonWindowManager).createSizeCompatButton(); + + final SizeCompatUIWindowManager hintWindowManager = mLayout.createHintWindowManager(); + spyOn(hintWindowManager); + doReturn(mHint).when(hintWindowManager).createSizeCompatHint(); + doReturn(hintWindowManager).when(mLayout).createHintWindowManager(); } @Test @@ -90,24 +98,45 @@ public class SizeCompatUILayoutTest extends ShellTestCase { // Not create button if IME is showing. mLayout.createSizeCompatButton(true /* isImeShowing */); - verify(mLayout.mWindowManager, never()).createSizeCompatUI(); + verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton(); assertNull(mLayout.mButton); + assertNull(mLayout.mHintWindowManager); + assertNull(mLayout.mHint); + // Not create hint popup. + mLayout.mShouldShowHint = false; mLayout.createSizeCompatButton(false /* isImeShowing */); - verify(mLayout.mWindowManager).createSizeCompatUI(); + verify(mLayout.mButtonWindowManager).createSizeCompatButton(); assertNotNull(mLayout.mButton); + assertNull(mLayout.mHintWindowManager); + assertNull(mLayout.mHint); + + // Create hint popup. + mLayout.release(); + mLayout.mShouldShowHint = true; + mLayout.createSizeCompatButton(false /* isImeShowing */); + + verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton(); + assertNotNull(mLayout.mButton); + assertNotNull(mLayout.mHintWindowManager); + verify(mLayout.mHintWindowManager).createSizeCompatHint(); + assertNotNull(mLayout.mHint); + assertFalse(mLayout.mShouldShowHint); } @Test public void testRelease() { mLayout.createSizeCompatButton(false /* isImeShowing */); + final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager; mLayout.release(); assertNull(mLayout.mButton); - verify(mButton).remove(); - verify(mLayout.mWindowManager).release(); + assertNull(mLayout.mHint); + verify(hintWindowManager).release(); + assertNull(mLayout.mHintWindowManager); + verify(mLayout.mButtonWindowManager).release(); } @Test @@ -119,7 +148,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase { mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, mTaskListener, false /* isImeShowing */); - verify(mLayout, never()).updateSurfacePosition(); + verify(mLayout, never()).updateButtonSurfacePosition(); verify(mLayout, never()).release(); verify(mLayout, never()).createSizeCompatButton(anyBoolean()); @@ -140,7 +169,8 @@ public class SizeCompatUILayoutTest extends ShellTestCase { mLayout.updateSizeCompatInfo(newTaskConfiguration, mActivityToken, newTaskListener, false /* isImeShowing */); - verify(mLayout).updateSurfacePosition(); + verify(mLayout).updateButtonSurfacePosition(); + verify(mLayout).updateHintSurfacePosition(); } @Test @@ -152,14 +182,16 @@ public class SizeCompatUILayoutTest extends ShellTestCase { mContext.getResources(), false, false); mLayout.updateDisplayLayout(displayLayout1); - verify(mLayout).updateSurfacePosition(); + verify(mLayout).updateButtonSurfacePosition(); + verify(mLayout).updateHintSurfacePosition(); // No update if the display bounds is the same. clearInvocations(mLayout); final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, mContext.getResources(), false, false); mLayout.updateDisplayLayout(displayLayout2); - verify(mLayout, never()).updateSurfacePosition(); + verify(mLayout, never()).updateButtonSurfacePosition(); + verify(mLayout, never()).updateHintSurfacePosition(); } @Test @@ -203,4 +235,29 @@ public class SizeCompatUILayoutTest extends ShellTestCase { verify(ActivityClient.getInstance()).restartActivityProcessIfVisible(mActivityToken); } + + @Test + public void testOnRestartButtonLongClicked_showHint() { + mLayout.dismissHint(); + + assertNull(mLayout.mHint); + + mLayout.onRestartButtonLongClicked(); + + assertNotNull(mLayout.mHint); + } + + @Test + public void testDismissHint() { + mLayout.onRestartButtonLongClicked(); + final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager; + assertNotNull(mLayout.mHint); + assertNotNull(hintWindowManager); + + mLayout.dismissHint(); + + assertNull(mLayout.mHint); + assertNull(mLayout.mHintWindowManager); + verify(hintWindowManager).release(); + } } |