Allow non-resizable apps in split-screen (12/n)

Before, the hint uses PopupWindow to be placed at the bottom right of the
Display, which can't be attached to Task. Now, render size compat button
 hint popup on Task surface with WindowlessWindowManager.

Fix: 178327644
Bug: 176061101
Test: atest WMShellUnitTests:SizeCompatUILayoutTest
Test: atest WMShellUnitTests:SizeCompatHintPopupTest
Change-Id: I6f8d7afc996d7f312516d85deb2bd113eae8bc3f
This commit is contained in:
Chris Li
2021-02-14 17:03:08 -08:00
parent e3ca0e420c
commit de551ad9b6
9 changed files with 432 additions and 191 deletions

View File

@@ -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" />
android:gravity="center"
android:clipToPadding="false"
android:padding="@dimen/bubble_elevation">
</LinearLayout>
<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>

View File

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

View File

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

View File

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

View File

@@ -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();
}
}
/** Releases the button window. */
/** Creates the restart button hint window. */
private void createSizeCompatHint() {
if (mHint != null) {
// Hint already shown.
return;
}
mHintWindowManager = createHintWindowManager();
mHint = mHintWindowManager.createSizeCompatHint();
updateHintSurfacePosition();
}
@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);
}
}

View File

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

View File

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

View File

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

View File

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