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:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user