Merge "Allow non-resizable apps in split-screen (8/n)" into sc-dev

This commit is contained in:
Chris Li
2021-02-03 00:38:22 +00:00
committed by Android (Google) Code Review
11 changed files with 413 additions and 433 deletions

View File

@@ -146,4 +146,10 @@
<!-- Content description to tell the user a bubble has been dismissed. -->
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
<string name="restart_button_description">Tap to restart this app and go full screen.</string>
<!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] -->
<string name="got_it">Got it</string>
</resources>

View File

@@ -0,0 +1,172 @@
/*
* 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.app.ActivityClient;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import com.android.wm.shell.R;
/** Button to restart the size compat activity. */
class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
View.OnLongClickListener {
private static final String TAG = "SizeCompatRestartButton";
final WindowManager.LayoutParams mWinParams;
final boolean mShouldShowHint;
final int mDisplayId;
final int mPopupOffsetX;
final int mPopupOffsetY;
private IBinder mLastActivityToken;
private PopupWindow mShowingHint;
SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
super(context);
mDisplayId = displayId;
mShouldShowHint = !hasShownHint;
final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
setImageDrawable(drawable);
setContentDescription(context.getString(R.string.restart_button_description));
final int drawableW = drawable.getIntrinsicWidth();
final int drawableH = drawable.getIntrinsicHeight();
mPopupOffsetX = drawableW / 2;
mPopupOffsetY = drawableH * 2;
final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
final GradientDrawable mask = new GradientDrawable();
mask.setShape(GradientDrawable.OVAL);
mask.setColor(color);
setBackground(new RippleDrawable(color, null /* content */, mask));
setOnClickListener(this);
setOnLongClickListener(this);
mWinParams = new WindowManager.LayoutParams();
mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
mWinParams.width = drawableW * 2;
mWinParams.height = drawableH * 2;
mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWinParams.format = PixelFormat.TRANSLUCENT;
mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
+ context.getDisplayId());
}
void updateLastTargetActivity(IBinder activityToken) {
mLastActivityToken = activityToken;
}
/** @return {@code false} if the target display is invalid. */
boolean show() {
try {
getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
} catch (WindowManager.InvalidDisplayException e) {
// The target display may have been removed when the callback has just arrived.
Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
return false;
}
return true;
}
void remove() {
if (mShowingHint != null) {
mShowingHint.dismiss();
}
getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
}
@Override
public void onClick(View v) {
ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
}
@Override
public boolean onLongClick(View v) {
showHint();
return true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mShouldShowHint) {
showHint();
}
}
@Override
public void setLayoutDirection(int layoutDirection) {
final int gravity = getGravity(layoutDirection);
if (mWinParams.gravity != gravity) {
mWinParams.gravity = gravity;
if (mShowingHint != null) {
mShowingHint.dismiss();
showHint();
}
getContext().getSystemService(WindowManager.class).updateViewLayout(this,
mWinParams);
}
super.setLayoutDirection(layoutDirection);
}
void showHint() {
if (mShowingHint != null) {
return;
}
final View popupView = LayoutInflater.from(getContext()).inflate(
R.layout.size_compat_mode_hint, null /* root */);
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 -> popupWindow.dismiss());
popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
}
private static int getGravity(int layoutDirection) {
return Gravity.BOTTOM
| (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
}
}

View File

@@ -19,7 +19,12 @@ package com.android.wm.shell.sizecompatui;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -27,6 +32,8 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
import java.lang.ref.WeakReference;
/**
* Shows a restart-activity button on Task when the foreground activity is in size compatibility
* mode.
@@ -35,6 +42,11 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
DisplayImeController.ImePositionProcessor {
private static final String TAG = "SizeCompatUI";
/** The showing buttons by task id. */
private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@VisibleForTesting
final SizeCompatUI mImpl = new SizeCompatUIImpl();
private final Context mContext;
@@ -42,6 +54,9 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
private final DisplayController mDisplayController;
private final DisplayImeController mImeController;
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
/** Creates the {@link SizeCompatUIController}. */
public static SizeCompatUI create(Context context,
DisplayController displayController,
@@ -67,16 +82,101 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
@Nullable IBinder sizeCompatActivity,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
// TODO need to deduplicate task info changed
// TODO Draw button on Task surface
if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
// Null token means the current foreground activity is not in size compatibility mode.
removeRestartButton(taskId);
} else {
updateRestartButton(displayId, taskId, sizeCompatActivity);
}
}
// TODO move from SizeCompatModeActivityController from system UI.
@Override
public void onDisplayRemoved(int displayId) {
mDisplayContextCache.remove(displayId);
for (int i = 0; i < mActiveButtons.size(); i++) {
final int taskId = mActiveButtons.keyAt(i);
final SizeCompatRestartButton button = mActiveButtons.get(taskId);
if (button != null && button.mDisplayId == displayId) {
removeRestartButton(taskId);
}
}
}
@Override
public void onImeVisibilityChanged(int displayId, boolean isShowing) {
final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
for (int i = 0; i < mActiveButtons.size(); i++) {
final int taskId = mActiveButtons.keyAt(i);
final SizeCompatRestartButton button = mActiveButtons.get(taskId);
if (button == null || button.mDisplayId != displayId) {
continue;
}
// Hide the button when input method is showing.
if (button.getVisibility() != newVisibility) {
button.setVisibility(newVisibility);
}
}
}
private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
if (restartButton != null) {
restartButton.updateLastTargetActivity(activityToken);
return;
}
final Context context = getOrCreateDisplayContext(displayId);
if (context == null) {
Log.i(TAG, "Cannot get context for display " + displayId);
return;
}
restartButton = createRestartButton(context, displayId);
restartButton.updateLastTargetActivity(activityToken);
if (restartButton.show()) {
mActiveButtons.append(taskId, restartButton);
} else {
onDisplayRemoved(displayId);
}
}
@VisibleForTesting
SizeCompatRestartButton createRestartButton(Context context, int displayId) {
final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
mHasShownHint);
// Only show hint for the first time.
mHasShownHint = true;
return button;
}
private void removeRestartButton(int taskId) {
final SizeCompatRestartButton button = mActiveButtons.get(taskId);
if (button != null) {
button.remove();
mActiveButtons.remove(taskId);
}
}
private Context getOrCreateDisplayContext(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
return mContext;
}
Context context = null;
final WeakReference<Context> ref = mDisplayContextCache.get(displayId);
if (ref != null) {
context = ref.get();
}
if (context == null) {
Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
if (display != null) {
context = mContext.createDisplayContext(display);
mDisplayContextCache.put(displayId, new WeakReference<>(context));
}
}
return context;
}
private class SizeCompatUIImpl implements SizeCompatUI {

View File

@@ -0,0 +1,123 @@
/*
* 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 org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests for {@link SizeCompatUIController}.
*
* Build/Install/Run:
* atest WMShellUnitTests:SizeCompatUIControllerTest
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class SizeCompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
private SizeCompatUIController mController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayImeController mMockImeController;
private @Mock SizeCompatRestartButton mMockButton;
private @Mock IBinder mMockActivityToken;
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(true).when(mMockButton).show();
mController = new SizeCompatUIController(mContext, mMockDisplayController,
mMockImeController, mShellMainExecutor) {
@Override
SizeCompatRestartButton createRestartButton(Context context, int displayId) {
return mMockButton;
}
};
}
@Test
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
}
@Test
public void testOnSizeCompatInfoChanged() {
final int taskId = 12;
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
// Verify that the restart button is added with non-null size compat activity.
mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
mMockActivityToken, mMockTaskListener);
mShellMainExecutor.flushAll();
verify(mMockButton).show();
verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
// Verify that the restart button is removed with null size compat activity.
mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
mShellMainExecutor.flushAll();
verify(mMockButton).remove();
}
@Test
public void testChangeButtonVisibilityOnImeShowHide() {
final int taskId = 12;
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
mMockActivityToken, mMockTaskListener);
mShellMainExecutor.flushAll();
// Verify that the restart button is hidden when IME is visible.
doReturn(View.VISIBLE).when(mMockButton).getVisibility();
mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
verify(mMockButton).setVisibility(eq(View.GONE));
// Verify that the restart button is visible when IME is hidden.
doReturn(View.GONE).when(mMockButton).getVisibility();
mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
verify(mMockButton).setVisibility(eq(View.VISIBLE));
}
}

View File

@@ -2580,9 +2580,6 @@
<!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] -->
<string name="music_controls_no_title">No title</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
<string name="restart_button_description">Tap to restart this app and go full screen.</string>
<!-- Action in accessibility menu to move the stack of bubbles [CHAR LIMIT=20] -->
<string name="bubble_accessibility_action_move">Move</string>

View File

@@ -16,284 +16,27 @@
package com.android.systemui;
import android.app.ActivityClient;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import java.lang.ref.WeakReference;
import javax.inject.Inject;
/** Shows a restart-activity button when the foreground activity is in size compatibility mode. */
/**
* Shows a restart-activity button when the foreground activity is in size compatibility mode.
*
* // TODO remove this class after cleanup all dependencies.
* @deprecated Use {@link com.android.wm.shell.sizecompatui.SizeCompatUIController}
*/
@Deprecated
@SysUISingleton
public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {
private static final String TAG = "SizeCompatMode";
public class SizeCompatModeActivityController extends SystemUI {
/** The showing buttons by display id. */
private final SparseArray<RestartActivityButton> mActiveButtons = new SparseArray<>(1);
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
private final CommandQueue mCommandQueue;
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
@VisibleForTesting
@Inject
SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
CommandQueue commandQueue) {
SizeCompatModeActivityController(Context context) {
super(context);
mCommandQueue = commandQueue;
listeners.registerTaskStackListener(new TaskStackChangeListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
// Note the callback already runs on main thread.
updateRestartButton(displayId, activityToken);
}
});
}
@Override
public void start() {
mCommandQueue.addCallback(this);
}
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
RestartActivityButton button = mActiveButtons.get(displayId);
if (button == null) {
return;
}
boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
int newVisibility = imeShown ? View.GONE : View.VISIBLE;
// Hide the button when input method is showing.
if (button.getVisibility() != newVisibility) {
button.setVisibility(newVisibility);
}
}
@Override
public void onDisplayRemoved(int displayId) {
mDisplayContextCache.remove(displayId);
removeRestartButton(displayId);
}
private void removeRestartButton(int displayId) {
RestartActivityButton button = mActiveButtons.get(displayId);
if (button != null) {
button.remove();
mActiveButtons.remove(displayId);
}
}
private void updateRestartButton(int displayId, IBinder activityToken) {
if (activityToken == null) {
// Null token means the current foreground activity is not in size compatibility mode.
removeRestartButton(displayId);
return;
}
RestartActivityButton restartButton = mActiveButtons.get(displayId);
if (restartButton != null) {
restartButton.updateLastTargetActivity(activityToken);
return;
}
Context context = getOrCreateDisplayContext(displayId);
if (context == null) {
Log.i(TAG, "Cannot get context for display " + displayId);
return;
}
restartButton = createRestartButton(context);
restartButton.updateLastTargetActivity(activityToken);
if (restartButton.show()) {
mActiveButtons.append(displayId, restartButton);
} else {
onDisplayRemoved(displayId);
}
}
@VisibleForTesting
RestartActivityButton createRestartButton(Context context) {
RestartActivityButton button = new RestartActivityButton(context, mHasShownHint);
mHasShownHint = true;
return button;
}
private Context getOrCreateDisplayContext(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
return mContext;
}
Context context = null;
WeakReference<Context> ref = mDisplayContextCache.get(displayId);
if (ref != null) {
context = ref.get();
}
if (context == null) {
Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
if (display != null) {
context = mContext.createDisplayContext(display);
mDisplayContextCache.put(displayId, new WeakReference<Context>(context));
}
}
return context;
}
@VisibleForTesting
static class RestartActivityButton extends ImageButton implements View.OnClickListener,
View.OnLongClickListener {
final WindowManager.LayoutParams mWinParams;
final boolean mShouldShowHint;
IBinder mLastActivityToken;
final int mPopupOffsetX;
final int mPopupOffsetY;
PopupWindow mShowingHint;
RestartActivityButton(Context context, boolean hasShownHint) {
super(context);
mShouldShowHint = !hasShownHint;
Drawable drawable = context.getDrawable(R.drawable.btn_restart);
setImageDrawable(drawable);
setContentDescription(context.getString(R.string.restart_button_description));
int drawableW = drawable.getIntrinsicWidth();
int drawableH = drawable.getIntrinsicHeight();
mPopupOffsetX = drawableW / 2;
mPopupOffsetY = drawableH * 2;
ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
GradientDrawable mask = new GradientDrawable();
mask.setShape(GradientDrawable.OVAL);
mask.setColor(color);
setBackground(new RippleDrawable(color, null /* content */, mask));
setOnClickListener(this);
setOnLongClickListener(this);
mWinParams = new WindowManager.LayoutParams();
mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
mWinParams.width = drawableW * 2;
mWinParams.height = drawableH * 2;
mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWinParams.format = PixelFormat.TRANSLUCENT;
mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
+ context.getDisplayId());
}
void updateLastTargetActivity(IBinder activityToken) {
mLastActivityToken = activityToken;
}
/** @return {@code false} if the target display is invalid. */
boolean show() {
try {
getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
} catch (WindowManager.InvalidDisplayException e) {
// The target display may have been removed when the callback has just arrived.
Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
return false;
}
return true;
}
void remove() {
if (mShowingHint != null) {
mShowingHint.dismiss();
}
getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
}
@Override
public void onClick(View v) {
ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
}
@Override
public boolean onLongClick(View v) {
showHint();
return true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mShouldShowHint) {
showHint();
}
}
@Override
public void setLayoutDirection(int layoutDirection) {
int gravity = getGravity(layoutDirection);
if (mWinParams.gravity != gravity) {
mWinParams.gravity = gravity;
if (mShowingHint != null) {
mShowingHint.dismiss();
showHint();
}
getContext().getSystemService(WindowManager.class).updateViewLayout(this,
mWinParams);
}
super.setLayoutDirection(layoutDirection);
}
void showHint() {
if (mShowingHint != null) {
return;
}
View popupView = LayoutInflater.from(getContext()).inflate(
R.layout.size_compat_mode_hint, null /* root */);
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;
Button gotItButton = popupView.findViewById(R.id.got_it);
gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
null /* content */, null /* mask */));
gotItButton.setOnClickListener(view -> popupWindow.dismiss());
popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
}
private static int getGravity(int layoutDirection) {
return Gravity.BOTTOM
| (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
}
}
public void start() { }
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright (C) 2019 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.systemui;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.SizeCompatModeActivityController.RestartActivityButton;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* runtest systemui -c com.android.systemui.SizeCompatModeActivityControllerTest
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
private static final int DISPLAY_ID = 0;
private SizeCompatModeActivityController mController;
private TaskStackChangeListener mTaskStackListener;
private @Mock TaskStackChangeListeners mMockTaskListeners;
private @Mock RestartActivityButton mMockButton;
private @Mock IBinder mMockActivityToken;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(true).when(mMockButton).show();
mController = new SizeCompatModeActivityController(mContext, mMockTaskListeners,
new CommandQueue(mContext)) {
@Override
RestartActivityButton createRestartButton(Context context) {
return mMockButton;
};
};
ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
ArgumentCaptor.forClass(TaskStackChangeListener.class);
verify(mMockTaskListeners).registerTaskStackListener(listenerCaptor.capture());
mTaskStackListener = listenerCaptor.getValue();
}
@Test
public void testOnSizeCompatModeActivityChanged() {
// Verifies that the restart button is added with non-null component name.
mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
verify(mMockButton).show();
verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
// Verifies that the restart button is removed with null component name.
mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, null /* activityToken */);
verify(mMockButton).remove();
}
@Test
public void testChangeButtonVisibilityOnImeShowHide() {
mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
// Verifies that the restart button is hidden when IME is visible.
doReturn(View.VISIBLE).when(mMockButton).getVisibility();
mController.setImeWindowStatus(DISPLAY_ID, null /* token */, InputMethodService.IME_VISIBLE,
0 /* backDisposition */, false /* showImeSwitcher */);
verify(mMockButton).setVisibility(eq(View.GONE));
// Verifies that the restart button is visible when IME is hidden.
doReturn(View.GONE).when(mMockButton).getVisibility();
mController.setImeWindowStatus(DISPLAY_ID, null /* token */, 0 /* vis */,
0 /* backDisposition */, false /* showImeSwitcher */);
verify(mMockButton).setVisibility(eq(View.VISIBLE));
}
}

View File

@@ -5500,12 +5500,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// The callback is only interested in the foreground changes of fullscreen activity.
return;
}
// TODO(b/178327644) Update for per Task size compat
if (!r.inSizeCompatMode()) {
if (mLastCompatModeActivity != null) {
// TODO(b/178327644) Remove notifySizeCompatModeActivityChanged
mAtmService.getTaskChangeNotificationController()
.notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
// This will do nothing until SizeCompatModeActivityController is moved to shell
organizedTask.onSizeCompatActivityChanged();
}
mLastCompatModeActivity = null;
@@ -5515,10 +5512,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return;
}
mLastCompatModeActivity = r;
// TODO(b/178327644) Remove notifySizeCompatModeActivityChanged
mAtmService.getTaskChangeNotificationController()
.notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
// This will do nothing until SizeCompatModeActivityController is moved to shell
organizedTask.onSizeCompatActivityChanged();
}

View File

@@ -52,14 +52,12 @@ import static org.mockito.Mockito.doCallRealMethod;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.TaskStackListener;
import android.app.WindowConfiguration;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
@@ -72,8 +70,6 @@ import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import java.util.ArrayList;
/**
* Tests for Size Compatibility mode.
*
@@ -478,52 +474,6 @@ public class SizeCompatTests extends WindowTestsBase {
eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
}
/**
* Ensures that {@link TaskStackListener} can receive callback about the activity in size
* compatibility mode.
*
* TODO(b/178327644) Remove after update DC#handleActivitySizeCompatModeIfNeeded
*/
@Test
public void testHandleActivitySizeCompatMode() {
setUpDisplaySizeWithApp(1000, 2000);
doReturn(true).when(mTask).isOrganized();
ActivityRecord activity = mActivity;
activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
final ArrayList<IBinder> compatTokens = new ArrayList<>();
mAtm.getTaskChangeNotificationController().registerTaskStackListener(
new TaskStackListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId,
IBinder activityToken) {
compatTokens.add(activityToken);
}
});
// Resize the display so that the activity exercises size-compat mode.
resizeDisplay(mTask.mDisplayContent, 1000, 2500);
// Expect the exact token when the activity is in size compatibility mode.
assertEquals(1, compatTokens.size());
assertEquals(activity.appToken, compatTokens.get(0));
compatTokens.clear();
// Make the activity resizable again by restarting it
activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
activity.mVisibleRequested = true;
activity.restartProcessIfVisible();
// The full lifecycle isn't hooked up so manually set state to resumed
activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
// Expect null token when switching to non-size-compat mode activity.
assertEquals(1, compatTokens.size());
assertEquals(null, compatTokens.get(0));
}
/**
* Ensures that {@link TaskOrganizerController} can receive callback about the activity in size
* compatibility mode.