diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 23db2335ecc07..774f6c6379b2c 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -115,4 +115,10 @@ + + + false + + + false diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java new file mode 100644 index 0000000000000..4f33a71b80d50 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 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.compatui; + +import android.content.Context; +import android.provider.DeviceConfig; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.R; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.dagger.WMSingleton; + +import javax.inject.Inject; + +/** + * Configuration flags for the CompatUX implementation + */ +@WMSingleton +public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener { + + static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog"; + + static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = + "enable_letterbox_reachability_education"; + + // Whether the extended restart dialog is enabled + private boolean mIsRestartDialogEnabled; + + // Whether the additional education about reachability is enabled + private boolean mIsReachabilityEducationEnabled; + + // Whether the extended restart dialog is enabled + private boolean mIsRestartDialogOverrideEnabled; + + // Whether the additional education about reachability is enabled + private boolean mIsReachabilityEducationOverrideEnabled; + + // Whether the extended restart dialog is allowed from backend + private boolean mIsLetterboxRestartDialogAllowed; + + // Whether the additional education about reachability is allowed from backend + private boolean mIsLetterboxReachabilityEducationAllowed; + + @Inject + public CompatUIConfiguration(Context context, @ShellMainThread ShellExecutor mainExecutor) { + mIsRestartDialogEnabled = context.getResources().getBoolean( + R.bool.config_letterboxIsRestartDialogEnabled); + mIsReachabilityEducationEnabled = context.getResources().getBoolean( + R.bool.config_letterboxIsReachabilityEducationEnabled); + mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false); + mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, + false); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor, + this); + } + + /** + * @return {@value true} if the restart dialog is enabled. + */ + boolean isRestartDialogEnabled() { + return mIsRestartDialogOverrideEnabled || (mIsRestartDialogEnabled + && mIsLetterboxRestartDialogAllowed); + } + + /** + * Enables/Disables the restart education dialog + */ + void setIsRestartDialogOverrideEnabled(boolean enabled) { + mIsRestartDialogOverrideEnabled = enabled; + } + + /** + * @return {@value true} if the reachability education is enabled. + */ + boolean isReachabilityEducationEnabled() { + return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled + && mIsLetterboxReachabilityEducationAllowed); + } + + /** + * Enables/Disables the reachability education + */ + void setIsReachabilityEducationOverrideEnabled(boolean enabled) { + mIsReachabilityEducationOverrideEnabled = enabled; + } + + @Override + public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + // TODO(b/263349751): Update flag and default value to true + if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) { + mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, + false); + } + if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) { + mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java new file mode 100644 index 0000000000000..4fb18e27b1456 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 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.compatui; + +import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.sysui.ShellCommandHandler; + +import java.io.PrintWriter; +import java.util.function.Consumer; + +import javax.inject.Inject; + +/** + * Handles the shell commands for the CompatUX. + * + *

Use with {@code adb shell dumpsys activity service SystemUIService WMShell compatui + * <command>}. + */ +@WMSingleton +public final class CompatUIShellCommandHandler implements + ShellCommandHandler.ShellCommandActionHandler { + + private final CompatUIConfiguration mCompatUIConfiguration; + private final ShellCommandHandler mShellCommandHandler; + + @Inject + public CompatUIShellCommandHandler(ShellCommandHandler shellCommandHandler, + CompatUIConfiguration compatUIConfiguration) { + mShellCommandHandler = shellCommandHandler; + mCompatUIConfiguration = compatUIConfiguration; + } + + void onInit() { + mShellCommandHandler.addCommandCallback("compatui", this, this); + } + + @Override + public boolean onShellCommand(String[] args, PrintWriter pw) { + if (args.length != 2) { + pw.println("Invalid command: " + args[0]); + return false; + } + switch (args[0]) { + case "restartDialogEnabled": + return invokeOrError(args[1], pw, + mCompatUIConfiguration::setIsRestartDialogOverrideEnabled); + case "reachabilityEducationEnabled": + return invokeOrError(args[1], pw, + mCompatUIConfiguration::setIsReachabilityEducationOverrideEnabled); + default: + pw.println("Invalid command: " + args[0]); + return false; + } + } + + @Override + public void printShellCommandHelp(PrintWriter pw, String prefix) { + pw.println(prefix + "restartDialogEnabled [0|false|1|true]"); + pw.println(prefix + " Enable/Disable the restart education dialog for Size Compat Mode"); + pw.println(prefix + "reachabilityEducationEnabled [0|false|1|true]"); + pw.println(prefix + + " Enable/Disable the restart education dialog for letterbox reachability"); + pw.println(prefix + " Disable the restart education dialog for letterbox reachability"); + } + + private static boolean invokeOrError(String input, PrintWriter pw, + Consumer setter) { + Boolean asBoolean = strToBoolean(input); + if (asBoolean == null) { + pw.println("Error: expected true, 1, false, 0."); + return false; + } + setter.accept(asBoolean); + return true; + } + + // Converts a String to boolean if possible or it returns null otherwise + private static Boolean strToBoolean(String str) { + switch(str) { + case "1": + case "true": + return true; + case "0": + case "false": + return false; + } + return null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java similarity index 85% rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java index 3061eab17d249..7475feac5b12d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.compatui.letterboxedu; +package com.android.wm.shell.compatui; import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation; @@ -38,10 +38,15 @@ import android.view.animation.Animation; import com.android.internal.policy.TransitionAnimation; /** - * Controls the enter/exit animations of the letterbox education. + * Controls the enter/exit a dialog. + * + * @param The {@link DialogContainerSupplier} to use */ -class LetterboxEduAnimationController { - private static final String TAG = "LetterboxEduAnimation"; +public class DialogAnimationController { + + // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque). + // 204 is simply 255 * 0.8. + static final int BACKGROUND_DIM_ALPHA = 204; // If shell transitions are enabled, startEnterAnimation will be called after all transitions // have finished, and therefore the start delay should be shorter. @@ -49,6 +54,7 @@ class LetterboxEduAnimationController { private final TransitionAnimation mTransitionAnimation; private final String mPackageName; + private final String mTag; @AnyRes private final int mAnimStyleResId; @@ -57,23 +63,24 @@ class LetterboxEduAnimationController { @Nullable private Animator mBackgroundDimAnimator; - LetterboxEduAnimationController(Context context) { - mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, TAG); + public DialogAnimationController(Context context, String tag) { + mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, tag); mAnimStyleResId = (new ContextThemeWrapper(context, android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes( com.android.internal.R.styleable.Window).getResourceId( com.android.internal.R.styleable.Window_windowAnimationStyle, 0); mPackageName = context.getPackageName(); + mTag = tag; } /** * Starts both background dim fade-in animation and the dialog enter animation. */ - void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) { + public void startEnterAnimation(@NonNull T layout, Runnable endCallback) { // Cancel any previous animation if it's still running. cancelAnimation(); - final View dialogContainer = layout.getDialogContainer(); + final View dialogContainer = layout.getDialogContainerView(); mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation); if (mDialogAnimation == null) { endCallback.run(); @@ -86,8 +93,8 @@ class LetterboxEduAnimationController { endCallback.run(); })); - mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), - /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA, + mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(), + /* endAlpha= */ BACKGROUND_DIM_ALPHA, mDialogAnimation.getDuration()); mBackgroundDimAnimator.addListener(getDimAnimatorListener()); @@ -101,11 +108,11 @@ class LetterboxEduAnimationController { /** * Starts both the background dim fade-out animation and the dialog exit animation. */ - void startExitAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) { + public void startExitAnimation(@NonNull T layout, Runnable endCallback) { // Cancel any previous animation if it's still running. cancelAnimation(); - final View dialogContainer = layout.getDialogContainer(); + final View dialogContainer = layout.getDialogContainerView(); mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation); if (mDialogAnimation == null) { endCallback.run(); @@ -119,8 +126,8 @@ class LetterboxEduAnimationController { endCallback.run(); })); - mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), /* endAlpha= */ 0, - mDialogAnimation.getDuration()); + mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(), + /* endAlpha= */ 0, mDialogAnimation.getDuration()); mBackgroundDimAnimator.addListener(getDimAnimatorListener()); dialogContainer.startAnimation(mDialogAnimation); @@ -130,7 +137,7 @@ class LetterboxEduAnimationController { /** * Cancels all animations and resets the state of the controller. */ - void cancelAnimation() { + public void cancelAnimation() { if (mDialogAnimation != null) { mDialogAnimation.cancel(); mDialogAnimation = null; @@ -145,7 +152,7 @@ class LetterboxEduAnimationController { Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId, animAttr, /* translucent= */ false); if (animation == null) { - Log.e(TAG, "Failed to load animation " + animAttr); + Log.e(mTag, "Failed to load animation " + animAttr); } return animation; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java new file mode 100644 index 0000000000000..7eea446fce265 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 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.compatui; + +import android.graphics.drawable.Drawable; +import android.view.View; + +/** + * A component which can provide a {@link View} to use as a container for a Dialog + */ +public interface DialogContainerSupplier { + + /** + * @return The {@link View} to use as a container for a Dialog + */ + View getDialogContainerView(); + + /** + * @return The {@link Drawable} to use as background of the dialog. + */ + Drawable getBackgroundDimDrawable(); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java index 2e0b09e9d230a..9232f36cf9397 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java @@ -26,6 +26,7 @@ import android.widget.TextView; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.wm.shell.R; +import com.android.wm.shell.compatui.DialogContainerSupplier; /** * Container for Letterbox Education Dialog and background dim. @@ -33,11 +34,7 @@ import com.android.wm.shell.R; *

This layout should fill the entire task and the background around the dialog acts as the * background dim which dismisses the dialog when clicked. */ -class LetterboxEduDialogLayout extends ConstraintLayout { - - // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque). - // 204 is simply 255 * 0.8. - static final int BACKGROUND_DIM_ALPHA = 204; +class LetterboxEduDialogLayout extends ConstraintLayout implements DialogContainerSupplier { private View mDialogContainer; private TextView mDialogTitle; @@ -60,18 +57,20 @@ class LetterboxEduDialogLayout extends ConstraintLayout { super(context, attrs, defStyleAttr, defStyleRes); } - View getDialogContainer() { + @Override + public View getDialogContainerView() { return mDialogContainer; } + @Override + public Drawable getBackgroundDimDrawable() { + return mBackgroundDim; + } + TextView getDialogTitle() { return mDialogTitle; } - Drawable getBackgroundDim() { - return mBackgroundDim; - } - /** * Register a callback for the dismiss button and background dim. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java index 867d0ef732ac9..c14c009721a1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java @@ -37,6 +37,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract; +import com.android.wm.shell.compatui.DialogAnimationController; import com.android.wm.shell.transition.Transitions; /** @@ -63,7 +64,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { */ private final SharedPreferences mSharedPreferences; - private final LetterboxEduAnimationController mAnimationController; + private final DialogAnimationController mAnimationController; private final Transitions mTransitions; @@ -96,14 +97,17 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback, DockStateReader dockStateReader) { this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions, - onDismissCallback, new LetterboxEduAnimationController(context), dockStateReader); + onDismissCallback, + new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"), + dockStateReader); } @VisibleForTesting LetterboxEduWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback, - LetterboxEduAnimationController animationController, DockStateReader dockStateReader) { + DialogAnimationController animationController, + DockStateReader dockStateReader) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mTransitions = transitions; mOnDismissCallback = onDismissCallback; @@ -160,7 +164,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { if (mLayout == null) { return; } - final View dialogContainer = mLayout.getDialogContainer(); + final View dialogContainer = mLayout.getDialogContainerView(); MarginLayoutParams marginParams = (MarginLayoutParams) dialogContainer.getLayoutParams(); final Rect taskBounds = getTaskBounds(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java index 6b59e313b01ba..d7cb490ed0cb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java @@ -16,8 +16,6 @@ package com.android.wm.shell.unfold; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; - import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; @@ -56,6 +54,12 @@ public class UnfoldAnimationController implements UnfoldListener { private final SparseArray mTaskSurfaces = new SparseArray<>(); private final SparseArray mAnimatorsByTaskId = new SparseArray<>(); + /** + * Indicates whether we're in stage change process. This should be set to {@code true} in + * {@link #onStateChangeStarted()} and {@code false} in {@link #onStateChangeFinished()}. + */ + private boolean mIsInStageChange; + public UnfoldAnimationController( @NonNull ShellInit shellInit, @NonNull TransactionPool transactionPool, @@ -123,7 +127,7 @@ public class UnfoldAnimationController implements UnfoldListener { animator.onTaskChanged(taskInfo); } else { // Became inapplicable - resetTask(animator, taskInfo); + maybeResetTask(animator, taskInfo); animator.onTaskVanished(taskInfo); mAnimatorsByTaskId.remove(taskInfo.taskId); } @@ -154,7 +158,7 @@ public class UnfoldAnimationController implements UnfoldListener { final boolean isCurrentlyApplicable = animator != null; if (isCurrentlyApplicable) { - resetTask(animator, taskInfo); + maybeResetTask(animator, taskInfo); animator.onTaskVanished(taskInfo); mAnimatorsByTaskId.remove(taskInfo.taskId); } @@ -166,6 +170,7 @@ public class UnfoldAnimationController implements UnfoldListener { return; } + mIsInStageChange = true; SurfaceControl.Transaction transaction = null; for (int i = 0; i < mAnimators.size(); i++) { final UnfoldTaskAnimator animator = mAnimators.get(i); @@ -219,11 +224,12 @@ public class UnfoldAnimationController implements UnfoldListener { transaction.apply(); mTransactionPool.release(transaction); + mIsInStageChange = false; } - private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) { - if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { - // PiP task has its own cleanup path, ignore surface reset to avoid conflict. + private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) { + if (!mIsInStageChange) { + // No need to resetTask if there is no ongoing state change. return; } final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java index 1dee88c438064..a58620dfc6dcf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java @@ -68,11 +68,11 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { @Test public void testOnFinishInflate() { - assertEquals(mLayout.getDialogContainer(), + assertEquals(mLayout.getDialogContainerView(), mLayout.findViewById(R.id.letterbox_education_dialog_container)); assertEquals(mLayout.getDialogTitle(), mLayout.findViewById(R.id.letterbox_education_dialog_title)); - assertEquals(mLayout.getBackgroundDim(), mLayout.getBackground()); + assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground()); assertEquals(mLayout.getBackground().getAlpha(), 0); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java index 16517c0a00103..14190f18929c2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java @@ -56,6 +56,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.DialogAnimationController; import com.android.wm.shell.transition.Transitions; import org.junit.After; @@ -98,7 +99,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Captor private ArgumentCaptor mRunOnIdleCaptor; - @Mock private LetterboxEduAnimationController mAnimationController; + @Mock private DialogAnimationController mAnimationController; @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private SurfaceControlViewHost mViewHost; @@ -366,7 +367,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { assertThat(params.width).isEqualTo(expectedWidth); assertThat(params.height).isEqualTo(expectedHeight); MarginLayoutParams dialogParams = - (MarginLayoutParams) layout.getDialogContainer().getLayoutParams(); + (MarginLayoutParams) layout.getDialogContainerView().getLayoutParams(); int verticalMargin = (int) mContext.getResources().getDimension( R.dimen.letterbox_education_dialog_margin); assertThat(dialogParams.topMargin).isEqualTo(verticalMargin + expectedExtraTopMargin); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 25fb24a3417fc..6b4d5ae86d1e9 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -31,6 +31,52 @@ license { ], } +// Opt-in configuration for code depending on Jetpack Compose. +soong_config_module_type { + name: "systemui_compose_java_defaults", + module_type: "java_defaults", + config_namespace: "ANDROID", + bool_variables: ["SYSTEMUI_USE_COMPOSE"], + properties: [ + "srcs", + "static_libs", + ], +} + +systemui_compose_java_defaults { + name: "SystemUI_compose_defaults", + soong_config_variables: { + SYSTEMUI_USE_COMPOSE: { + // Because files in compose/features/ depend on SystemUI + // code, we compile those files when compiling SystemUI-core. + // We also compile the ComposeFacade in + // compose/facade/enabled/. + srcs: [ + "compose/features/src/**/*.kt", + "compose/facade/enabled/src/**/*.kt", + ], + + // The dependencies needed by SystemUIComposeFeatures, + // except for SystemUI-core. + // Copied from compose/features/Android.bp. + static_libs: [ + "SystemUIComposeCore", + + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "androidx.activity_activity-compose", + ], + + // By default, Compose is disabled and we compile the ComposeFacade + // in compose/facade/disabled/. + conditions_default: { + srcs: ["compose/facade/disabled/src/**/*.kt"], + static_libs: [], + }, + }, + }, +} + java_library { name: "SystemUI-proto", @@ -68,6 +114,9 @@ filegroup { android_library { name: "SystemUI-core", + defaults: [ + "SystemUI_compose_defaults", + ], srcs: [ "src/**/*.kt", "src/**/*.java", @@ -227,6 +276,9 @@ android_library { android_library { name: "SystemUI-tests", + defaults: [ + "SystemUI_compose_defaults", + ], manifest: "tests/AndroidManifest-base.xml", additional_manifests: ["tests/AndroidManifest.xml"], srcs: [ diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/runtime/MovableContent.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/runtime/MovableContent.kt new file mode 100644 index 0000000000000..3f2f96b293e0a --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/runtime/MovableContent.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.compose.runtime + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.InternalComposeApi +import androidx.compose.runtime.MovableContent +import androidx.compose.runtime.currentComposer + +/** + * An overload of [androidx.compose.runtime.movableContentOf] with 5 parameters. + * + * @see androidx.compose.runtime.movableContentOf + */ +@OptIn(InternalComposeApi::class) +fun movableContentOf( + content: @Composable (P1, P2, P3, P4, P5) -> Unit +): @Composable (P1, P2, P3, P4, P5) -> Unit { + val movableContent = + MovableContent, Pair>> { + content( + it.first.first, + it.first.second, + it.first.third, + it.second.first, + it.second.second, + ) + } + return { p1, p2, p3, p4, p5 -> + currentComposer.insertMovableContent(movableContent, Triple(p1, p2, p3) to (p4 to p5)) + } +} diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt new file mode 100644 index 0000000000000..6e728ce7248f1 --- /dev/null +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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.compose + +import androidx.activity.ComponentActivity +import com.android.systemui.people.ui.viewmodel.PeopleViewModel + +/** The Compose facade, when Compose is *not* available. */ +object ComposeFacade : BaseComposeFacade { + override fun isComposeAvailable(): Boolean = false + + override fun setPeopleSpaceActivityContent( + activity: ComponentActivity, + viewModel: PeopleViewModel, + onResult: (PeopleViewModel.Result) -> Unit, + ) { + throwComposeUnavailableError() + } + + private fun throwComposeUnavailableError() { + error( + "Compose is not available. Make sure to check isComposeAvailable() before calling any" + + " other function on ComposeFacade." + ) + } +} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt new file mode 100644 index 0000000000000..16294d9c19772 --- /dev/null +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 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.compose + +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import com.android.systemui.compose.theme.SystemUITheme +import com.android.systemui.people.ui.compose.PeopleScreen +import com.android.systemui.people.ui.viewmodel.PeopleViewModel + +/** The Compose facade, when Compose is available. */ +object ComposeFacade : BaseComposeFacade { + override fun isComposeAvailable(): Boolean = true + + override fun setPeopleSpaceActivityContent( + activity: ComponentActivity, + viewModel: PeopleViewModel, + onResult: (PeopleViewModel.Result) -> Unit, + ) { + activity.setContent { SystemUITheme { PeopleScreen(viewModel, onResult) } } + } +} diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index 325ede613de8e..4533330531180 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -35,6 +35,7 @@ android_library { "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", + "androidx.activity_activity-compose", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index 2aac46e903426..4a56b027e07c3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -139,11 +139,20 @@ private fun PeopleScreenWithConversations( bottom = PeopleSpacePadding, start = 8.dp, end = 8.dp, - ) + ), ) { - ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked) - item { Spacer(Modifier.height(35.dp)) } - ConversationList(R.string.recent_conversations, recentTiles, onTileClicked) + val hasPriorityConversations = priorityTiles.isNotEmpty() + if (hasPriorityConversations) { + ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked) + } + + if (recentTiles.isNotEmpty()) { + if (hasPriorityConversations) { + item { Spacer(Modifier.height(35.dp)) } + } + + ConversationList(R.string.recent_conversations, recentTiles, onTileClicked) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt new file mode 100644 index 0000000000000..e5ec727f04379 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 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.compose + +import androidx.activity.ComponentActivity +import com.android.systemui.people.ui.viewmodel.PeopleViewModel + +/** + * A facade to interact with Compose, when it is available. + * + * You should access this facade by calling the static methods on + * [com.android.systemui.compose.ComposeFacade] directly. + */ +interface BaseComposeFacade { + /** + * Whether Compose is currently available. This function should be checked before calling any + * other functions on this facade. + * + * This value will never change at runtime. + */ + fun isComposeAvailable(): Boolean + + /** Bind the content of [activity] to [viewModel]. */ + fun setPeopleSpaceActivityContent( + activity: ComponentActivity, + viewModel: PeopleViewModel, + onResult: (PeopleViewModel.Result) -> Unit, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 7cc95a158a140..fba5f63ea9c78 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -27,11 +27,15 @@ import android.view.ViewGroup; import androidx.activity.ComponentActivity; import androidx.lifecycle.ViewModelProvider; +import com.android.systemui.compose.ComposeFacade; import com.android.systemui.people.ui.view.PeopleViewBinder; import com.android.systemui.people.ui.viewmodel.PeopleViewModel; import javax.inject.Inject; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + /** People Tile Widget configuration activity that shows the user their conversation tiles. */ public class PeopleSpaceActivity extends ComponentActivity { @@ -58,13 +62,18 @@ public class PeopleSpaceActivity extends ComponentActivity { int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); viewModel.onWidgetIdChanged(widgetId); - ViewGroup view = PeopleViewBinder.create(this); - PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, - (result) -> { - finishActivity(result); - return null; - }); - setContentView(view); + Function1 onResult = (result) -> { + finishActivity(result); + return null; + }; + + if (ComposeFacade.INSTANCE.isComposeAvailable()) { + ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult); + } else { + ViewGroup view = PeopleViewBinder.create(this); + PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult); + setContentView(view); + } } private void finishActivity(PeopleViewModel.Result result) {