Merge "Allow non-resizable apps in split-screen (12/n)" into sc-dev
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