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 eee0128bb9564..81b7a7b4aaffb 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 d5d06f9a00332..35260d3da0e2e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -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(); } 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 bbd89b8d01732..cc4d4eaa9e8b6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -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 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.