From 27d983a098b616d6eb14b2d559ee6d0f1322fb11 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Sat, 30 Jan 2021 22:11:44 -0800 Subject: [PATCH] Allow non-resizable apps in split-screen (8/n) Refactor SizeCompatModeActivityController to WM Shell. The old one will be removed in separate cl as there are some dependencies in other packages. This cl is mainly for refactor. Major change will come after. Besides refactor, also changed the mActiveButtons key to TaskId, because launcher Task will also trigger onTaskInfoChanged, but should not remove the restart button on the same display. Bug: 176061101 Bug: 178327644 Test: manually verify that the size compat restart button works as usual Test: atest WMShellUnitTests:SizeCompatUIControllerTest Test: atest WmTests:SizeCompatTests Change-Id: I9143e079a1a945b76c3c56596976dd6ad2802897 --- .../drawable/size_compat_restart_button.xml | 0 .../res/layout/size_compat_mode_hint.xml | 0 .../Shell/res/values/strings.xml | 6 + .../sizecompatui/SizeCompatRestartButton.java | 172 +++++++++++ .../sizecompatui/SizeCompatUIController.java | 102 ++++++- .../SizeCompatUIControllerTest.java | 123 ++++++++ packages/SystemUI/res/values/strings.xml | 3 - .../SizeCompatModeActivityController.java | 277 +----------------- .../SizeCompatModeActivityControllerTest.java | 104 ------- .../com/android/server/wm/DisplayContent.java | 9 +- .../android/server/wm/SizeCompatTests.java | 50 ---- 11 files changed, 413 insertions(+), 433 deletions(-) rename packages/SystemUI/res/drawable/btn_restart.xml => libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml (100%) rename {packages/SystemUI => libs/WindowManager/Shell}/res/layout/size_compat_mode_hint.xml (100%) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java diff --git a/packages/SystemUI/res/drawable/btn_restart.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml similarity index 100% rename from packages/SystemUI/res/drawable/btn_restart.xml rename to libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml diff --git a/packages/SystemUI/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml similarity index 100% rename from packages/SystemUI/res/layout/size_compat_mode_hint.xml rename to libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 30ef72c2a3521..b1425e4eeb287 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -146,4 +146,10 @@ Bubble dismissed. + + + Tap to restart this app and go full screen. + + + Got it diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java new file mode 100644 index 0000000000000..e47e1ac71c733 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java @@ -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); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java index 44f391c953a09..8cb16c87ce6f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java @@ -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 mActiveButtons = new SparseArray<>(1); + /** Avoid creating display context frequently for non-default display. */ + private final SparseArray> 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 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 { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java new file mode 100644 index 0000000000000..98f01ff08debc --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java @@ -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)); + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4baa06a393a85..5f1afda0afca1 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2580,9 +2580,6 @@ No title - - Tap to restart this app and go full screen. - Move diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java index 5384ddfd18bf6..7a52d270f92c4 100644 --- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java @@ -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 mActiveButtons = new SparseArray<>(1); - /** Avoid creating display context frequently for non-default display. */ - private final SparseArray> 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 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; - } - - @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() { } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java deleted file mode 100644 index 71a0434c41b42..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java +++ /dev/null @@ -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 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)); - } -} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 11cc2c6e856d6..9582fd24c3c95 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5491,12 +5491,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; @@ -5506,10 +5503,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(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 6dfbbc700e9ad..f6e614201a0de 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -51,14 +51,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; @@ -71,8 +69,6 @@ import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; -import java.util.ArrayList; - /** * Tests for Size Compatibility mode. * @@ -477,52 +473,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 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.