Snap for 9434600 from d5a4b9e514 to tm-qpr2-release

Change-Id: I07c013ec90fe86a51c905b4aea0a3ef86409e92f
This commit is contained in:
Android Build Coastguard Worker
2022-12-24 02:20:30 +00:00
18 changed files with 571 additions and 52 deletions

View File

@@ -115,4 +115,10 @@
<!-- Components support to launch multiple instances into split-screen -->
<string-array name="config_appsSupportMultiInstancesSplit">
</string-array>
<!-- Whether the extended restart dialog is enabled -->
<bool name="config_letterboxIsRestartDialogEnabled">false</bool>
<!-- Whether the additional education about reachability is enabled -->
<bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
</resources>

View File

@@ -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);
}
}
}

View File

@@ -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.
*
* <p> Use with {@code adb shell dumpsys activity service SystemUIService WMShell compatui
* &lt;command&gt;}.
*/
@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<Boolean> 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;
}
}

View File

@@ -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 <T> The {@link DialogContainerSupplier} to use
*/
class LetterboxEduAnimationController {
private static final String TAG = "LetterboxEduAnimation";
public class DialogAnimationController<T extends DialogContainerSupplier> {
// 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;
}

View File

@@ -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();
}

View File

@@ -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;
* <p>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.
*

View File

@@ -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<LetterboxEduDialogLayout> 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<LetterboxEduDialogLayout> 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();

View File

@@ -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<SurfaceControl> mTaskSurfaces = new SparseArray<>();
private final SparseArray<UnfoldTaskAnimator> 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();

View File

@@ -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);
}

View File

@@ -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<Runnable> mRunOnIdleCaptor;
@Mock private LetterboxEduAnimationController mAnimationController;
@Mock private DialogAnimationController<LetterboxEduDialogLayout> 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);

View File

@@ -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: [

View File

@@ -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 <P1, P2, P3, P4, P5> movableContentOf(
content: @Composable (P1, P2, P3, P4, P5) -> Unit
): @Composable (P1, P2, P3, P4, P5) -> Unit {
val movableContent =
MovableContent<Pair<Triple<P1, P2, P3>, Pair<P4, P5>>> {
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))
}
}

View File

@@ -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."
)
}
}

View File

@@ -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) } }
}
}

View File

@@ -35,6 +35,7 @@ android_library {
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
"androidx.activity_activity-compose",
],
kotlincflags: ["-Xjvm-default=all"],

View File

@@ -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)
}
}
}
}

View File

@@ -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,
)
}

View File

@@ -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<PeopleViewModel.Result, Unit> 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) {