Merge Android 24Q1 Release (ab/11220357)
Bug: 319669529
Merged-In: I46c7859ff042ee7aa9193757e5df8269f4892362
Change-Id: I0c7b5036c0b0f5f2caad551edb063350f6eb87e7
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 9da6c10..ed99501 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -16,11 +16,14 @@
package androidx.window.extensions;
+import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
+import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.extensions.area.WindowAreaComponent;
@@ -38,12 +41,17 @@
*/
public class WindowExtensionsImpl implements WindowExtensions {
+ private static final String TAG = "WindowExtensionsImpl";
private final Object mLock = new Object();
private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
private volatile WindowLayoutComponentImpl mWindowLayoutComponent;
private volatile SplitController mSplitController;
private volatile WindowAreaComponent mWindowAreaComponent;
+ public WindowExtensionsImpl() {
+ Log.i(TAG, "Initializing Window Extensions.");
+ }
+
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
@@ -104,9 +112,13 @@
* {@link WindowExtensions#getWindowLayoutComponent()}.
* @return {@link ActivityEmbeddingComponent} OEM implementation.
*/
- @NonNull
+ @Nullable
public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
if (mSplitController == null) {
+ if (!ActivityTaskManager.supportsMultiWindow(getApplication())) {
+ // Disable AE for device that doesn't support multi window.
+ return null;
+ }
synchronized (mLock) {
if (mSplitController == null) {
mSplitController = new SplitController(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 15d14e8..b315f94 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -241,7 +241,7 @@
for (int i = 0; i < displays.length; i++) {
DisplayAddress.Physical address =
(DisplayAddress.Physical) displays[i].getAddress();
- if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+ if (address != null && mRearDisplayAddress == address.getPhysicalDisplayId()) {
rearDisplayMetrics = new DisplayMetrics();
final Display rearDisplay = displays[i];
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 4d73c20..ca3d8d1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -375,7 +375,8 @@
return TaskFragmentAnimationParams.DEFAULT;
}
return new TaskFragmentAnimationParams.Builder()
- .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ // TODO(b/263047900): Update extensions API.
+ // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
.build();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 0000000..ff49cdc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 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 androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+ // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS =
+ "androidx.window.extensions.OverlayCreateParams";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+ "androidx.window.extensions.OverlayCreateParams.taskId";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+ "androidx.window.extensions.OverlayCreateParams.tag";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+ "androidx.window.extensions.OverlayCreateParams.bounds";
+
+ private final int mTaskId;
+
+ @NonNull
+ private final String mTag;
+
+ @NonNull
+ private final Rect mBounds;
+
+ OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+ mTaskId = taskId;
+ mTag = requireNonNull(tag);
+ mBounds = requireNonNull(bounds);
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ @NonNull
+ String getTag() {
+ return mTag;
+ }
+
+ @NonNull
+ Rect getBounds() {
+ return mBounds;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTaskId;
+ result = 31 * result + mTag.hashCode();
+ result = 31 * result + mBounds.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+ return mTaskId == thatParams.mTaskId
+ && mTag.equals(thatParams.mTag)
+ && mBounds.equals(thatParams.mBounds);
+ }
+
+ @Override
+ public String toString() {
+ return OverlayCreateParams.class.getSimpleName() + ": {"
+ + "taskId=" + mTaskId
+ + ", tag=" + mTag
+ + ", bounds=" + mBounds
+ + "}";
+ }
+
+ /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+ @Nullable
+ static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+ final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+ if (paramsBundle == null) {
+ return null;
+ }
+ final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+ final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+ final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+ KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+ return new OverlayCreateParams(taskId, tag, bounds);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 381e9d4..08b7bb8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -25,6 +25,7 @@
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.extensions.core.util.function.Function;
/**
@@ -186,10 +187,21 @@
return (mSplitRule instanceof SplitPlaceholderRule);
}
- @NonNull
- SplitInfo toSplitInfo() {
- return new SplitInfo(mPrimaryContainer.toActivityStack(),
- mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
+ /**
+ * Returns the SplitInfo representing this container.
+ *
+ * @return the SplitInfo representing this container if the underlying TaskFragmentContainers
+ * are stable, or {@code null} if any TaskFragmentContainer is in an intermediate state.
+ */
+ @Nullable
+ SplitInfo toSplitInfoIfStable() {
+ final ActivityStack primaryActivityStack = mPrimaryContainer.toActivityStackIfStable();
+ final ActivityStack secondaryActivityStack = mSecondaryContainer.toActivityStackIfStable();
+ if (primaryActivityStack == null || secondaryActivityStack == null) {
+ return null;
+ }
+ return new SplitInfo(primaryActivityStack, secondaryActivityStack,
+ mCurrentSplitAttributes, mToken);
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3ad3045..4973a4d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,10 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -87,6 +88,7 @@
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,8 +125,7 @@
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
* <ul>
- * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
- * WindowContainerTransaction)}</li>
+ * <li>{@link SplitPresenter#updateSplitContainer}</li>
* <li>There's a started Activity which matches {@link SplitPairRule} </li>
* <li>Checking whether the place holder should be launched if there's a Activity matches
* {@link SplitPlaceholderRule} </li>
@@ -155,6 +156,7 @@
public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
+ Log.i(TAG, "Initializing Activity Embedding Controller.");
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
@@ -207,6 +209,7 @@
@Override
public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
synchronized (mLock) {
+ Log.i(TAG, "Setting embedding rules. Size: " + rules.size());
mSplitRules.clear();
mSplitRules.addAll(rules);
}
@@ -215,6 +218,7 @@
@Override
public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
synchronized (mLock) {
+ Log.i(TAG, "Request to pin top activity stack.");
final TaskContainer task = getTaskContainer(taskId);
if (task == null) {
Log.e(TAG, "Cannot find the task for id: " + taskId);
@@ -271,6 +275,7 @@
@Override
public void unpinTopActivityStack(int taskId){
synchronized (mLock) {
+ Log.i(TAG, "Request to unpin top activity stack.");
final TaskContainer task = getTaskContainer(taskId);
if (task == null) {
Log.e(TAG, "Cannot find the task to unpin, id: " + taskId);
@@ -291,8 +296,8 @@
// Resets the isolated navigation and updates the container.
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.setTaskFragmentIsolatedNavigation(wct,
- containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
+ false /* isolated */);
updateContainer(wct, containerToUnpin);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
@@ -759,6 +764,8 @@
if (targetContainer == null) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+ // container.
targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
}
if (targetContainer == null) {
@@ -877,13 +884,9 @@
return true;
}
- // Skip resolving if the activity is on a pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
- if (container != null) {
- final TaskContainer taskContainer = container.getTaskContainer();
- if (taskContainer.isTaskFragmentContainerPinned(container)) {
- return true;
- }
+ // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
+ if (container != null && container.isIsolatedNavigationEnabled()) {
+ return true;
}
final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
@@ -891,10 +894,32 @@
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
// Do not resolve if the launched activity is not the top-most container (excludes
- // the pinned container) in the Task.
+ // the pinned and overlay container) in the Task.
return true;
}
+ // Ensure the top TaskFragments are updated to the right config if activity is resolved
+ // to a new TaskFragment while pin TF exists.
+ final boolean handled = resolveActivityToContainerByRule(wct, activity, container,
+ isOnReparent);
+ if (handled && taskContainer != null) {
+ final SplitPinContainer splitPinContainer = taskContainer.getSplitPinContainer();
+ if (splitPinContainer != null) {
+ final TaskFragmentContainer resolvedContainer = getContainerWithActivity(activity);
+ if (resolvedContainer != null && resolvedContainer.getRunningActivityCount() <= 1) {
+ updateContainer(wct, splitPinContainer.getSecondaryContainer());
+ }
+ }
+ }
+ return handled;
+ }
+
+ /**
+ * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
+ */
+ boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, @Nullable TaskFragmentContainer container,
+ boolean isOnReparent) {
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new launched activity should always expand.
@@ -987,6 +1012,7 @@
if (taskContainer == null) {
return;
}
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
final TaskFragmentContainer targetContainer =
taskContainer.getTopNonFinishingTaskFragmentContainer();
if (targetContainer == null) {
@@ -1146,7 +1172,7 @@
getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule(),
- getTaskWindowMetrics(taskProperties.getConfiguration()),
+ taskProperties.getTaskMetrics(),
calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
@@ -1258,9 +1284,9 @@
// Check whether the Intent should be embedded in the known Task.
final TaskContainer taskContainer = mTaskContainers.valueAt(0);
if (taskContainer.isInPictureInPicture()
- || taskContainer.getTopNonFinishingActivity() == null) {
+ || taskContainer.getTopNonFinishingActivity(false /* includeOverlay */) == null) {
// We don't embed activity when it is in PIP, or if we can't find any other owner
- // activity in the Task.
+ // activity in non-overlay container in the Task.
return null;
}
@@ -1288,19 +1314,36 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
- final TaskContainer taskContainer =
- taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
- if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
- taskFragmentContainer)) {
+ if (taskFragmentContainer != null
+ && taskFragmentContainer.isIsolatedNavigationEnabled()) {
return null;
}
}
+ // Ensure the top TaskFragments are updated to the right config if the intent is resolved
+ // to a new TaskFragment while pin TF exists.
+ final TaskFragmentContainer launchingContainer = resolveStartActivityIntentByRule(wct,
+ taskId, intent, launchingActivity);
+ if (launchingContainer != null && launchingContainer.getRunningActivityCount() == 0) {
+ final SplitPinContainer splitPinContainer =
+ launchingContainer.getTaskContainer().getSplitPinContainer();
+ if (splitPinContainer != null) {
+ updateContainer(wct, splitPinContainer.getSecondaryContainer());
+ }
+ }
+ return launchingContainer;
+ }
+
+ /**
+ * Resolves the intent to a {@link TaskFragmentContainer} according to the Split-rules.
+ */
+ @Nullable
+ TaskFragmentContainer resolveStartActivityIntentByRule(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new activity intent should always expand.
@@ -1368,6 +1411,22 @@
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
+ return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ null /* overlayTag */);
+ }
+
+ /**
+ * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+ * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+ * overlay container.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createEmptyContainer(
+ @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+ @NonNull Rect bounds, @Nullable Activity launchingActivity,
+ @Nullable String overlayTag) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1376,20 +1435,53 @@
} else {
final TaskContainer taskContainer = getTaskContainer(taskId);
activityInTask = taskContainer != null
- ? taskContainer.getTopNonFinishingActivity()
+ ? taskContainer.getTopNonFinishingActivity(true /* includeOverlay */)
: null;
}
if (activityInTask == null) {
// Can't find any activity in the Task that we can use as the owner activity.
return null;
}
- final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
- taskId);
- mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
- activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
- mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ final IBinder taskFragmentToken = container.getTaskFragmentToken();
+ // Note that taskContainer will not exist before calling #newContainer if the container
+ // is the first embedded TF in the task.
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ final int windowingMode = taskContainer
+ .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+ mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+ sanitizedBounds, windowingMode);
+ mPresenter.updateAnimationParams(wct, taskFragmentToken,
TaskFragmentAnimationParams.DEFAULT);
- return expandedContainer;
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+ overlayTag != null && !sanitizedBounds.isEmpty());
+
+ return container;
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
/**
@@ -1409,8 +1501,7 @@
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
final TaskContainer.TaskProperties taskProperties = mPresenter
.getTaskProperties(primaryActivity);
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
- taskProperties.getConfiguration());
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1479,14 +1570,22 @@
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ }
+
+ @GuardedBy("mLock")
+ TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+ @NonNull Activity activityInTask, int taskId,
+ @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+ return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
}
/**
@@ -1500,11 +1599,14 @@
* @param taskId parent Task of the new TaskFragment.
* @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
* set, the new container will be added right above it.
+ * @param overlayTag The tag for the new created overlay container. It must be
+ * needed if {@code isOverlay} is {@code true}. Otherwise,
+ * it should be {@code null}.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1513,7 +1615,7 @@
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
return container;
}
@@ -1653,31 +1755,6 @@
}
/**
- * Returns the topmost not finished container in Task of given task id.
- */
- @GuardedBy("mLock")
- @Nullable
- TaskFragmentContainer getTopActiveContainer(int taskId) {
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
- if (taskContainer == null) {
- return null;
- }
- final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
- for (int i = containers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = containers.get(i);
- if (!container.isFinished() && (container.getRunningActivityCount() > 0
- // We may be waiting for the top TaskFragment to become non-empty after
- // creation. In that case, we don't want to treat the TaskFragment below it as
- // top active, otherwise it may incorrectly launch placeholder on top of the
- // pending TaskFragment.
- || container.isWaitingActivityAppear())) {
- return container;
- }
- }
- return null;
- }
-
- /**
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
@@ -1690,6 +1767,11 @@
return;
}
+ if (container.isOverlay()) {
+ updateOverlayContainer(wct, container);
+ return;
+ }
+
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1710,17 +1792,35 @@
updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
}
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ final TaskContainer taskContainer = container.getTaskContainer();
+ // Dismiss the overlay container if it's the only container in the task and there's no
+ // direct activity in the parent task.
+ if (taskContainer.getTaskFragmentContainers().size() == 1
+ && !taskContainer.hasDirectActivity()) {
+ container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+ }
+
+ // TODO(b/295805054): Add the logic to update overlay container
+ }
+
/**
* Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
* {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
* are {@code null}, the {@link SplitAttributes} will be calculated with
- * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+ * {@link SplitPresenter#computeSplitAttributes}.
*
* @param splitContainer The {@link SplitContainer} to update
* @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
* Otherwise, use the value calculated by
- * {@link SplitPresenter#computeSplitAttributes(
- * TaskContainer.TaskProperties, SplitRule, Pair)}
+ * {@link SplitPresenter#computeSplitAttributes}
*
* @return {@code true} if the update succeed. Otherwise, returns {@code false}.
*/
@@ -1866,7 +1966,8 @@
/** Whether or not to allow activity in this container to launch placeholder. */
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
- final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+ final TaskFragmentContainer topContainer = container.getTaskContainer()
+ .getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
// The container is not the top most.
if (!container.isVisible()) {
@@ -1972,8 +2073,8 @@
if (mEmbeddingCallback == null || !readyToReportToClient()) {
return;
}
- final List<SplitInfo> currentSplitStates = getActiveSplitStates();
- if (mLastReportedSplitStates.equals(currentSplitStates)) {
+ final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
+ if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
return;
}
mLastReportedSplitStates.clear();
@@ -1983,13 +2084,21 @@
/**
* Returns a list of descriptors for currently active split states.
+ *
+ * @return a list of descriptors for currently active split states if all the containers are in
+ * a stable state, or {@code null} otherwise.
*/
@GuardedBy("mLock")
- @NonNull
- private List<SplitInfo> getActiveSplitStates() {
+ @Nullable
+ private List<SplitInfo> getActiveSplitStatesIfStable() {
final List<SplitInfo> splitStates = new ArrayList<>();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).getSplitStates(splitStates);
+ final List<SplitInfo> taskSplitStates =
+ mTaskContainers.valueAt(i).getSplitStatesIfStable();
+ if (taskSplitStates == null) {
+ return null;
+ }
+ splitStates.addAll(taskSplitStates);
}
return splitStates;
}
@@ -2207,6 +2316,96 @@
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
}
+ /**
+ * Gets all overlay containers from all tasks in this process, or an empty list if there's
+ * no overlay container.
+ * <p>
+ * Note that we only support one overlay container for each task, but an app could have multiple
+ * tasks.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+ final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+ for (int i = 0; i < mTaskContainers.size(); i++) {
+ final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+ final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+ if (overlayContainer != null) {
+ overlayContainers.add(overlayContainer);
+ }
+ }
+ return overlayContainers;
+ }
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull Intent intent, @NonNull Activity launchActivity) {
+ final int taskId = overlayCreateParams.getTaskId();
+ if (taskId != launchTaskId) {
+ // The task ID doesn't match the launch activity's. Cannot determine the host task
+ // to launch the overlay.
+ throw new IllegalArgumentException("The task ID of "
+ + "OverlayCreateParams#launchingActivity must match the task ID of "
+ + "the activity to #startActivity with the activity options that takes "
+ + "OverlayCreateParams.");
+ }
+ final List<TaskFragmentContainer> overlayContainers =
+ getAllOverlayTaskFragmentContainers();
+ final String overlayTag = overlayCreateParams.getTag();
+
+ // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+ // specified by Intent, expand the overlay container to fill the parent task instead.
+ final Rect bounds = overlayCreateParams.getBounds();
+ final Size minDimensions = getMinDimensions(intent);
+ final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+ minDimensions);
+ if (!overlayContainers.isEmpty()) {
+ for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+ if (!overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with different tag shown in the same
+ // task, dismiss the existing overlay container.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId != overlayContainer.getTaskId()) {
+ // If there's an overlay container with same tag in a different task,
+ // dismiss the overlay container since the tag must be unique per process.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with the same tag and task ID, we treat
+ // the OverlayCreateParams as the update to the container.
+ final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+ .getTaskMetrics().getBounds();
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+ !sanitizedBounds.isEmpty());
+ // We can just return the updated overlay container and don't need to
+ // check other condition since we only have one OverlayCreateParams, and
+ // if the tag and task are matched, it's impossible to match another task
+ // or tag since tags and tasks are all unique.
+ return overlayContainer;
+ }
+ }
+ }
+ return createEmptyContainer(wct, intent, taskId,
+ (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ }
+
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
@@ -2369,8 +2568,16 @@
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
- launchingActivity);
+ final OverlayCreateParams overlayCreateParams =
+ OverlayCreateParams.fromBundle(options);
+ if (Flags.activityEmbeddingOverlayPresentationFlag()
+ && overlayCreateParams != null) {
+ launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+ overlayCreateParams, taskId, intent, launchingActivity);
+ } else {
+ launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+ launchingActivity);
+ }
} else {
launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
intent);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index d894487..b5c32bb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
import android.view.View;
-import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(
- null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
+ launchingActivity, taskId,
// Pass in the primary container to make sure it is added right above the primary.
primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
@@ -390,14 +388,27 @@
return;
}
- setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
- !isStacked /* isolatedNav */);
+ setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
if (isStacked && !splitPinRule.isSticky()) {
secondaryContainer.getTaskContainer().removeSplitPinContainer();
}
}
/**
+ * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+ */
+ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer taskFragmentContainer,
+ boolean isolatedNavigationEnabled) {
+ if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ return;
+ }
+ taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ isolatedNavigationEnabled);
+ }
+
+ /**
* Resizes the task fragment if it was already registered. Skips the operation if the container
* creation has not been reported from the server yet.
*/
@@ -618,7 +629,7 @@
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
@Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +724,15 @@
return new Size(windowLayout.minWidth, windowLayout.minHeight);
}
- private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+ static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
@Nullable Size minDimensions) {
if (minDimensions == null) {
return false;
}
+ // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+ if (bounds.isEmpty()) {
+ return false;
+ }
return bounds.width() < minDimensions.getWidth()
|| bounds.height() < minDimensions.getHeight();
}
@@ -839,7 +854,8 @@
return new SplitAttributes.Builder()
.setSplitType(splitTypeToUpdate)
.setLayoutDirection(splitAttributes.getLayoutDirection())
- .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ // TODO(b/263047900): Update extensions API.
+ // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
.build();
}
@@ -1066,14 +1082,6 @@
@NonNull
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
- return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
- }
-
- @NonNull
- static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
- final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
- // TODO(b/190433398): Supply correct insets.
- final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ return getTaskProperties(activity).getTaskMetrics();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 463c8ce..028e75f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@
@Nullable
private SplitPinContainer mSplitPinContainer;
+ /** The overlay container in this Task. */
+ @Nullable
+ private TaskFragmentContainer mOverlayContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -68,6 +75,8 @@
private boolean mIsVisible;
+ private boolean mHasDirectActivity;
+
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
* the organizer receives
@@ -95,8 +104,9 @@
mConfiguration = taskProperties.getConfiguration();
mDisplayId = taskProperties.getDisplayId();
// Note that it is always called when there's a new Activity is started, which implies
- // the host task is visible.
+ // the host task is visible and has an activity in the task.
mIsVisible = true;
+ mHasDirectActivity = true;
}
int getTaskId() {
@@ -111,6 +121,10 @@
return mIsVisible;
}
+ boolean hasDirectActivity() {
+ return mHasDirectActivity;
+ }
+
@NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
@@ -120,6 +134,7 @@
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
mIsVisible = info.isVisible();
+ mHasDirectActivity = info.hasDirectActivity();
}
/**
@@ -184,11 +199,20 @@
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+ return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin,
+ boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
if (!includePin && isTaskFragmentContainerPinned(container)) {
continue;
}
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
@@ -211,9 +235,13 @@
}
@Nullable
- Activity getTopNonFinishingActivity() {
+ Activity getTopNonFinishingActivity(boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
- final Activity activity = mContainers.get(i).getTopNonFinishingActivity();
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
+ final Activity activity = container.getTopNonFinishingActivity();
if (activity != null) {
return activity;
}
@@ -221,6 +249,12 @@
return null;
}
+ /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+ @Nullable
+ TaskFragmentContainer getOverlayContainer() {
+ return mOverlayContainer;
+ }
+
int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(child);
}
@@ -311,8 +345,8 @@
onTaskFragmentContainerUpdated();
}
- void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
- mContainers.removeAll(taskFragmentContainer);
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+ mContainers.removeAll(taskFragmentContainers);
onTaskFragmentContainerUpdated();
}
@@ -332,6 +366,15 @@
}
private void onTaskFragmentContainerUpdated() {
+ // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+ // another special container that should also be on top in the future.
+ updateSplitPinContainerIfNecessary();
+ // Update overlay container after split pin container since the overlay should be on top of
+ // pin container.
+ updateOverlayContainerIfNecessary();
+ }
+
+ private void updateSplitPinContainerIfNecessary() {
if (mSplitPinContainer == null) {
return;
}
@@ -344,10 +387,7 @@
}
// Ensure the pinned container is top-most.
- if (pinnedContainerIndex != mContainers.size() - 1) {
- mContainers.remove(pinnedContainer);
- mContainers.add(pinnedContainer);
- }
+ moveContainerToLastIfNecessary(pinnedContainer);
// Update the primary container adjacent to the pinned container if needed.
final TaskFragmentContainer adjacentContainer =
@@ -359,11 +399,48 @@
}
}
- /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
- void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
- for (SplitContainer container : mSplitContainers) {
- outSplitStates.add(container.toSplitInfo());
+ private void updateOverlayContainerIfNecessary() {
+ final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+ .filter(TaskFragmentContainer::isOverlay).toList();
+ if (overlayContainers.size() > 1) {
+ throw new IllegalStateException("There must be at most one overlay container per Task");
}
+ mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+ if (mOverlayContainer != null) {
+ moveContainerToLastIfNecessary(mOverlayContainer);
+ }
+ }
+
+ /** Moves the {@code container} to the last to align taskFragments' z-order. */
+ private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+ final int index = mContainers.indexOf(container);
+ if (index < 0) {
+ Log.w(TAG, "The container:" + container + " is not in the container list!");
+ return;
+ }
+ if (index != mContainers.size() - 1) {
+ mContainers.remove(container);
+ mContainers.add(container);
+ }
+ }
+
+ /**
+ * Gets the descriptors of split states in this Task.
+ *
+ * @return a list of {@code SplitInfo} if all the SplitContainers are stable, or {@code null} if
+ * any SplitContainer is in an intermediate state.
+ */
+ @Nullable
+ List<SplitInfo> getSplitStatesIfStable() {
+ final List<SplitInfo> splitStates = new ArrayList<>();
+ for (SplitContainer container : mSplitContainers) {
+ final SplitInfo splitInfo = container.toSplitInfoIfStable();
+ if (splitInfo == null) {
+ return null;
+ }
+ splitStates.add(splitInfo);
+ }
+ return splitStates;
}
/** A wrapper class which contains the information of {@link TaskContainer} */
@@ -386,6 +463,15 @@
return mConfiguration;
}
+ /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+ @NonNull
+ WindowMetrics getTaskMetrics() {
+ final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+ // TODO(b/190433398): Supply correct insets.
+ final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ }
+
/** Translates the given absolute bounds to relative bounds in this Task coordinate. */
void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
if (inOutBounds.isEmpty()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 61df335..3e7f99b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+ @Nullable
+ private final String mOverlayTag;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -157,15 +160,32 @@
*/
private boolean mHasCrossProcessActivities;
+ /** Whether this TaskFragment enable isolated navigation. */
+ private boolean mIsIsolatedNavigationEnabled;
+
+ /**
+ * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+ * TaskFragmentContainer, String)
+ */
+ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+ @Nullable Intent pendingAppearedIntent,
+ @NonNull TaskContainer taskContainer,
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+ controller, pairedPrimaryContainer, null /* overlayTag */);
+ }
+
/**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
+ * @param overlayTag Sets to indicate this taskFragment is an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -174,6 +194,8 @@
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
+ mOverlayTag = overlayTag;
+
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -217,6 +239,30 @@
/** List of non-finishing activities that belong to this container and live in this process. */
@NonNull
List<Activity> collectNonFinishingActivities() {
+ final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */);
+ if (activities == null) {
+ throw new IllegalStateException(
+ "Result activities should never be null when checkIfstable is false.");
+ }
+ return activities;
+ }
+
+ /**
+ * Collects non-finishing activities that belong to this container and live in this process.
+ *
+ * @param checkIfStable if {@code true}, returns {@code null} when the container is in an
+ * intermediate state.
+ * @return List of non-finishing activities that belong to this container and live in this
+ * process, {@code null} if checkIfStable is {@code true} and the container is in an
+ * intermediate state.
+ */
+ @Nullable
+ List<Activity> collectNonFinishingActivities(boolean checkIfStable) {
+ if (checkIfStable
+ && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) {
+ return null;
+ }
+
final List<Activity> allActivities = new ArrayList<>();
if (mInfo != null) {
// Add activities reported from the server.
@@ -224,6 +270,15 @@
final Activity activity = mController.getActivity(token);
if (activity != null && !activity.isFinishing()) {
allActivities.add(activity);
+ } else {
+ if (checkIfStable) {
+ // Return null except for a special case when the activity is started in
+ // background.
+ if (activity == null && !mTaskContainer.isVisible()) {
+ continue;
+ }
+ return null;
+ }
}
}
}
@@ -277,9 +332,19 @@
return false;
}
- @NonNull
- ActivityStack toActivityStack() {
- return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
+ /**
+ * Returns the ActivityStack representing this container.
+ *
+ * @return ActivityStack representing this container if it is in a stable state. {@code null} if
+ * in an intermediate state.
+ */
+ @Nullable
+ ActivityStack toActivityStackIfStable() {
+ final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */);
+ if (activities == null) {
+ return null;
+ }
+ return new ActivityStack(activities, isEmpty(), mToken);
}
/** Adds the activity that will be reparented to this container. */
@@ -743,6 +808,16 @@
mLastCompanionTaskFragment = fragmentToken;
}
+ /** Returns whether to enable isolated navigation or not. */
+ boolean isIsolatedNavigationEnabled() {
+ return mIsIsolatedNavigationEnabled;
+ }
+
+ /** Sets whether to enable isolated navigation or not. */
+ void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
+ mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
+ }
+
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
@@ -820,6 +895,20 @@
return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
}
+ /** Returns whether this taskFragment container is an overlay container. */
+ boolean isOverlay() {
+ return mOverlayTag != null;
+ }
+
+ /**
+ * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+ * taskFragment container is not an overlay container.
+ */
+ @Nullable
+ String getOverlayTag() {
+ return mOverlayTag;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
@@ -838,6 +927,7 @@
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ + " overlayTag=" + mOverlayTag
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a1fe7f7..9b84a48 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.isZero;
@@ -94,14 +95,6 @@
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- /** Registers to listen to {@link CommonFoldingFeature} changes */
- public void addFoldingStateChangedCallback(
- java.util.function.Consumer<List<CommonFoldingFeature>> consumer) {
- synchronized (mLock) {
- mFoldingFeatureProducer.addDataChangedCallback(consumer);
- }
- }
-
/**
* Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
*
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2d..4ddbd13 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"truth",
"testables",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index a0590dc..60beb0b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -16,8 +16,11 @@
package androidx.window.extensions;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.google.common.truth.Truth.assertThat;
+import android.app.ActivityTaskManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,7 +55,11 @@
@Test
public void testGetActivityEmbeddingComponent() {
- assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ if (ActivityTaskManager.supportsMultiWindow(getInstrumentation().getContext())) {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ } else {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNull();
+ }
}
@Test
@@ -63,6 +70,7 @@
.isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
assertThat(splitAttributes.getSplitType())
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
- assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ // TODO(b/263047900): Update extensions API.
+ // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
new file mode 100644
index 0000000..4c2433f
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2023 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 androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+ private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+ new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+ private SplitController.ActivityStartMonitor mMonitor;
+
+ private Intent mIntent;
+
+ private TaskFragmentContainer mOverlayContainer1;
+
+ private TaskFragmentContainer mOverlayContainer2;
+
+ private Activity mActivity;
+ @Mock
+ private Resources mActivityResources;
+
+ @Mock
+ private WindowContainerTransaction mTransaction;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+ private SplitController mSplitController;
+ private SplitPresenter mSplitPresenter;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ mSplitController = new SplitController(mWindowLayoutComponent, producer);
+ mSplitPresenter = mSplitController.mPresenter;
+ mMonitor = mSplitController.getActivityStartMonitor();
+ mIntent = new Intent();
+
+ spyOn(mSplitController);
+ spyOn(mSplitPresenter);
+ spyOn(mMonitor);
+
+ doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+ final Configuration activityConfig = new Configuration();
+ activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+ doReturn(activityConfig).when(mActivityResources).getConfiguration();
+ doReturn(mHandler).when(mSplitController).getHandler();
+ mActivity = createMockActivity();
+
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ @NonNull
+ private Activity createMockActivity() {
+ final Activity activity = mock(Activity.class);
+ doReturn(mActivityResources).when(activity).getResources();
+ final IBinder activityToken = new Binder();
+ doReturn(activityToken).when(activity).getActivityToken();
+ doReturn(activity).when(mSplitController).getActivity(activityToken);
+ doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ return activity;
+ }
+
+ @Test
+ public void testOverlayCreateParamsFromBundle() {
+ assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+ assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+ .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+ }
+
+ @Test
+ public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+ mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+ mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+ verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+ anyInt(), any(), any());
+ }
+
+ @NonNull
+ private static Bundle createOverlayCreateParamsTestBundle() {
+ final Bundle bundle = new Bundle();
+
+ final Bundle paramsBundle = new Bundle();
+ paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+ TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+ paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+ paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+ TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+ bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+ return bundle;
+ }
+
+ @Test
+ public void testGetOverlayContainers() {
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+ final TaskFragmentContainer overlayContainer1 =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1);
+
+ assertThrows(
+ "The exception must throw if there are two overlay containers in the same task.",
+ IllegalStateException.class,
+ () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+ final TaskFragmentContainer overlayContainer3 =
+ createTestOverlayContainer(TASK_ID + 1, "test3");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1, overlayContainer3);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+ assertThrows("The method must return null due to task mismatch between"
+ + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+ () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched to the same task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+ TASK_ID + 2);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched with the same tag as an existing overlay container in a different "
+ + "task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+ createExistingOverlayContainers();
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test1", bounds),
+ TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ + " is launched with the same tag and task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+ assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+ verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+ eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+ TASK_ID);
+
+ // OverlayContainer1 is dismissed since new container is launched in the same task with
+ // different tag. OverlayContainer2 is dismissed since new container is launched with the
+ // same tag in different task.
+ assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ private void createExistingOverlayContainers() {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+ List<TaskFragmentContainer> overlayContainers = mSplitController
+ .getAllOverlayTaskFragmentContainers();
+ assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ final Rect bounds = new Rect(TASK_BOUNDS);
+ bounds.offset(10, 10);
+ final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+ "test", bounds);
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ paramsOutsideTaskBounds, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+ assertThat(overlayContainer
+ .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ }
+
+ @Test
+ public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ // Add a SplitPinContainer, the overlay should be on top
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity);
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.pinTopActivityStack(TASK_ID, splitPinRule);
+ final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID)
+ .getSplitPinContainer().getSecondaryContainer();
+
+ // Add a normal container after the overlay, the overlay should still on top,
+ // and the SplitPinContainer should also on top of the normal one.
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+ assertThat(taskContainer.getTaskFragmentContainers())
+ .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer)
+ .inOrder();
+
+ assertWithMessage("The pinned container must be returned excluding the overlay")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer())
+ .isEqualTo(topPinnedContainer);
+
+ assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false))
+ .isEqualTo(container);
+
+ assertWithMessage("The overlay container must be returned since it's always on top")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer(
+ false /* includePin */, true /* includeOverlay */))
+ .isEqualTo(overlayContainer);
+ }
+
+ @Test
+ public void testGetTopNonFinishingActivityWithOverlay() {
+ createTestOverlayContainer(TASK_ID, "test1");
+ final Activity activity = createMockActivity();
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
+ final TaskContainer task = container.getTaskContainer();
+
+ assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity);
+ assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity);
+ }
+
+ @Test
+ public void testUpdateContainer_dontInvokeUpdateOverlayForNonOverlayContainer() {
+ TaskFragmentContainer taskFragmentContainer = createMockTaskFragmentContainer(mActivity);
+
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+ verify(mSplitController, never()).updateOverlayContainer(any(), any());
+ }
+
+ @Test
+ public void testUpdateOverlayContainer_dismissOverlayIfNeeded() {
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+
+ mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+ assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer);
+
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY,
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */));
+
+ mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+ assertWithMessage("The overlay must be dismissed since there's no activity"
+ + " in the task and other taskFragment.")
+ .that(taskContainer.getTaskFragmentContainers()).isEmpty();
+ }
+
+ /**
+ * A simplified version of {@link SplitController.ActivityStartMonitor
+ * #createOrUpdateOverlayTaskFragmentIfNeeded}
+ */
+ @Nullable
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull OverlayCreateParams params, int taskId) {
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+ taskId, mIntent, mActivity);
+ }
+
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
+ setupTaskFragmentInfo(container, activity);
+ return container;
+ }
+
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+ null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+ null /* pairedPrimaryContainer */, tag);
+ setupTaskFragmentInfo(overlayContainer, mActivity);
+ return overlayContainer;
+ }
+
+ private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+ container.setInfo(mTransaction, info);
+ mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index b2ffad7..8c274a2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -48,18 +48,18 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -175,52 +175,6 @@
}
@Test
- public void testGetTopActiveContainer() {
- final TaskContainer taskContainer = createTestTaskContainer();
- // tf1 has no running activity so is not active.
- final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
- // tf2 has running activity so is active.
- final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
- doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.addTaskFragmentContainer(tf2);
- // tf3 is finished so is not active.
- final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
- doReturn(true).when(tf3).isFinished();
- doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.addTaskFragmentContainer(tf3);
- mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
-
- assertWithMessage("Must return tf2 because tf3 is not active.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf3);
-
- assertWithMessage("Must return tf2 because tf2 has running activity.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf2);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- doReturn(new ArrayList<>()).when(info).getActivities();
- doReturn(true).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
- + " creation.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- doReturn(false).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return null because tf1 becomes empty.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
- }
-
- @Test
public void testOnTaskFragmentVanished() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
@@ -305,7 +259,9 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).getTopActiveContainer(TASK_ID);
+ TaskContainer taskContainer = tf.getTaskContainer();
+ spyOn(taskContainer);
+ verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer();
// Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -320,7 +276,7 @@
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer = mSplitController.getTaskContainer(TASK_ID);
taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -595,13 +551,12 @@
}
@Test
- public void testResolveStartActivityIntent_skipIfPinned() {
+ public void testResolveStartActivityIntent_skipIfIsolatedNavEnabled() {
final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
- final TaskContainer taskContainer = container.getTaskContainer();
- spyOn(taskContainer);
+ container.setIsolatedNavigationEnabled(true);
+
final Intent intent = new Intent();
setupSplitRule(mActivity, intent);
- doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
mActivity));
}
@@ -634,7 +589,8 @@
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
}
@Test
@@ -796,7 +752,8 @@
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -838,7 +795,8 @@
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -1181,7 +1139,8 @@
public void testOnTransactionReady_taskFragmentParentInfoChanged() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
- DEFAULT_DISPLAY, true);
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */);
transaction.addChange(new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(TASK_ID)
@@ -1286,6 +1245,34 @@
}
@Test
+ public void testSplitInfoCallback_NotReportSplitIfUnstable() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+
+ // Should report new SplitInfo list if stable.
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(1, mSplitInfos.size());
+
+ // Should not report new SplitInfo list if unstable, e.g. any Activity is finishing.
+ mSplitInfos.clear();
+ final Activity r2 = createMockActivity();
+ final Activity r3 = createMockActivity();
+ doReturn(true).when(r2).isFinishing();
+ addSplitTaskFragments(r2, r3);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertTrue(mSplitInfos.isEmpty());
+
+ // Should report SplitInfo list if it becomes stable again.
+ mSplitInfos.clear();
+ doReturn(false).when(r2).isFinishing();
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(2, mSplitInfos.size());
+ }
+
+ @Test
public void testSplitInfoCallback_reportSplitInMultipleTasks() {
final int taskId0 = 1;
final int taskId1 = 2;
@@ -1537,9 +1524,9 @@
addSplitTaskFragments(primaryActivity, thirdActivity);
// Ensure another SplitContainer is added and the pinned TaskFragment still on top
- assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
- assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
- == secondaryActivity);
+ assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
+ assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
+ .getTopNonFinishingActivity(), secondaryActivity);
}
/** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 000c65a..7b77235 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -48,6 +48,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
/**
* Test class for {@link TaskContainer}.
*
@@ -77,14 +79,16 @@
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */));
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */));
assertEquals(WINDOWING_MODE_FREEFORM,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -104,13 +108,15 @@
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */));
assertFalse(taskContainer.isInPictureInPicture());
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */));
assertTrue(taskContainer.isInPictureInPicture());
}
@@ -149,20 +155,54 @@
@Test
public void testGetTopNonFinishingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
- assertNull(taskContainer.getTopNonFinishingActivity());
+ assertNull(taskContainer.getTopNonFinishingActivity(true /* includeOverlay */));
final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
taskContainer.addTaskFragmentContainer(tf0);
final Activity activity0 = mock(Activity.class);
doReturn(activity0).when(tf0).getTopNonFinishingActivity();
- assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
+ assertEquals(activity0, taskContainer.getTopNonFinishingActivity(
+ true /* includeOverlay */));
final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
taskContainer.addTaskFragmentContainer(tf1);
- assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
+ assertEquals(activity0, taskContainer.getTopNonFinishingActivity(
+ true /* includeOverlay */));
final Activity activity1 = mock(Activity.class);
doReturn(activity1).when(tf1).getTopNonFinishingActivity();
- assertEquals(activity1, taskContainer.getTopNonFinishingActivity());
+ assertEquals(activity1, taskContainer.getTopNonFinishingActivity(
+ true /* includeOverlay */));
+ }
+
+ @Test
+ public void testGetSplitStatesIfStable() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+
+ final SplitContainer splitContainer0 = mock(SplitContainer.class);
+ final SplitContainer splitContainer1 = mock(SplitContainer.class);
+ final SplitInfo splitInfo0 = mock(SplitInfo.class);
+ final SplitInfo splitInfo1 = mock(SplitInfo.class);
+ taskContainer.addSplitContainer(splitContainer0);
+ taskContainer.addSplitContainer(splitContainer1);
+
+ // When all the SplitContainers are stable, getSplitStatesIfStable() returns the list of
+ // SplitInfo representing the SplitContainers.
+ doReturn(splitInfo0).when(splitContainer0).toSplitInfoIfStable();
+ doReturn(splitInfo1).when(splitContainer1).toSplitInfoIfStable();
+
+ List<SplitInfo> splitInfoList = taskContainer.getSplitStatesIfStable();
+
+ assertEquals(2, splitInfoList.size());
+ assertEquals(splitInfo0, splitInfoList.get(0));
+ assertEquals(splitInfo1, splitInfoList.get(1));
+
+ // When any SplitContainer is in an intermediate state, getSplitStatesIfStable() returns
+ // null.
+ doReturn(null).when(splitContainer0).toSplitInfoIfStable();
+
+ splitInfoList = taskContainer.getSplitStatesIfStable();
+
+ assertNull(splitInfoList);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 78b85e6..cc00a49 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -321,6 +321,32 @@
}
@Test
+ public void testCollectNonFinishingActivities_checkIfStable() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+
+ // In case mInfo is null, collectNonFinishingActivities(true) should return null.
+ List<Activity> activities =
+ container.collectNonFinishingActivities(true /* checkIfStable */);
+ assertNull(activities);
+
+ // collectNonFinishingActivities(true) should return proper value when the container is in a
+ // stable state.
+ final List<IBinder> runningActivities = Lists.newArrayList(mActivity.getActivityToken());
+ doReturn(runningActivities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+ activities = container.collectNonFinishingActivities(true /* checkIfStable */);
+ assertEquals(1, activities.size());
+
+ // In case any activity is finishing, collectNonFinishingActivities(true) should return
+ // null.
+ doReturn(true).when(mActivity).isFinishing();
+ activities = container.collectNonFinishingActivities(true /* checkIfStable */);
+ assertNull(activities);
+ }
+
+ @Test
public void testAddPendingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index e9abc7e..5ad144d 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -151,6 +151,7 @@
static_libs: [
"androidx.appcompat_appcompat",
"androidx.core_core-animation",
+ "androidx.core_core-ktx",
"androidx.arch.core_core-runtime",
"androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
@@ -158,6 +159,8 @@
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"iconloader_base",
+ "com_android_wm_shell_flags_lib",
+ "com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
"dagger2",
"jsr330",
@@ -170,4 +173,5 @@
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
}
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
new file mode 100644
index 0000000..1a98ffc
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "com_android_wm_shell_flags",
+ package: "com.android.wm.shell",
+ srcs: [
+ "multitasking.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com_android_wm_shell_flags_lib",
+ aconfig_declarations: "com_android_wm_shell_flags",
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
new file mode 100644
index 0000000..4511f3b
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -0,0 +1,59 @@
+package: "com.android.wm.shell"
+
+flag {
+ name: "example_flag"
+ namespace: "multitasking"
+ description: "An Example Flag"
+ bug: "300136750"
+}
+
+flag {
+ name: "enable_app_pairs"
+ namespace: "multitasking"
+ description: "Enables the ability to create and save app pairs to the Home screen"
+ bug: "274835596"
+}
+
+flag {
+ name: "enable_desktop_windowing"
+ namespace: "multitasking"
+ description: "Enables desktop windowing"
+ bug: "304778354"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_split_contextual"
+ namespace: "multitasking"
+ description: "Enables invoking split contextually"
+ bug: "276361926"
+}
+
+flag {
+ name: "enable_taskbar_navbar_unification"
+ namespace: "multitasking"
+ description: "Enables taskbar / navbar unification"
+ bug: "309671494"
+}
+
+flag {
+ name: "enable_pip_ui_state_on_entering"
+ namespace: "multitasking"
+ description: "Enables PiP UI state callback on entering"
+ bug: "303718131"
+}
+
+flag {
+ name: "enable_pip2_implementation"
+ namespace: "multitasking"
+ description: "Enables the new implementation of PiP (PiP2)"
+ bug: "290220798"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_left_right_split_in_portrait"
+ namespace: "multitasking"
+ description: "Enables left/right split in portrait"
+ bug: "291018646"
+}
diff --git a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
index c82a70c..5c58158 100644
--- a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
+++ b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
@@ -48,7 +48,7 @@
optional int32 handler = 3;
optional int64 merge_time_ns = 4;
optional int64 merge_request_time_ns = 5;
- optional int32 merged_into = 6;
+ optional int32 merge_target = 6;
optional int64 abort_time_ns = 7;
}
diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml
new file mode 100644
index 0000000..65f5239
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:state_hovered="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:state_focused="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:state_selected="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:color="@color/desktop_mode_maximize_menu_button"/>
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml
new file mode 100644
index 0000000..86679af
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:state_hovered="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:state_focused="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:state_selected="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:color="@color/desktop_mode_maximize_menu_button_outline"/>
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml
index d2360e9..657720e 100644
--- a/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml
@@ -21,7 +21,7 @@
<solid
android:color="?androidprv:attr/materialColorSurfaceContainerHigh"
/>
- <corners android:radius="20dp" />
+ <corners android:radius="18sp" />
<padding
android:left="20dp"
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml
similarity index 79%
rename from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
rename to libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml
index 4ee10f4..15837ad 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml
@@ -15,7 +15,8 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<corners android:radius="@dimen/desktop_mode_handle_menu_corner_radius" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
similarity index 77%
rename from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
rename to libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
index ef30060..5d9fe67 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -15,8 +15,7 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_title_color"
- xmlns:android="http://schemas.android.com/apk/res/android">
+ xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
+ <corners android:radius="@dimen/desktop_mode_maximize_menu_corner_radius" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml
new file mode 100644
index 0000000..bfb0dd7
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/>
+ <corners
+ android:radius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"/>
+ <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml
new file mode 100644
index 0000000..6630fca
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2023 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
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/>
+ <corners
+ android:topLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"
+ android:topRightRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"
+ android:bottomLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"
+ android:bottomRightRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"/>
+ <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml
new file mode 100644
index 0000000..7bd6e99
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2023 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
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/>
+ <corners
+ android:topLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"
+ android:topRightRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"
+ android:bottomLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"
+ android:bottomRightRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"/>
+ <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
index 3e0297a..e9df936 100644
--- a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
+++ b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
@@ -14,8 +14,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<vector android:height="24dp" android:tint="#000000"
- android:viewportHeight="24" android:viewportWidth="24"
- android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@android:color/black" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorSecondary">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17"/>
</vector>
+
diff --git a/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
new file mode 100644
index 0000000..8ef3307
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M4,18H20V6H4V18ZM22,18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20C21.1,4 22,4.9 22,6V18ZM13,8H18V14H13V8Z"
+ android:fillColor="#455A64"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
new file mode 100644
index 0000000..b489a5c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<com.android.wm.shell.common.bubbles.BubblePopupView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/bubble_popup_margin_horizontal"
+ android:layout_marginBottom="120dp"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:tint="?android:attr/colorAccent"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_floating_landscape"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/bubble_bar_education_stack_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="center"
+ android:text="@string/bubble_bar_education_stack_text"/>
+
+</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 10c9562..d8ae9c8 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -26,7 +26,8 @@
android:id="@+id/bubble_manage_menu_dismiss_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="@dimen/bubble_menu_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
android:paddingStart="@dimen/bubble_menu_padding"
android:paddingEnd="@dimen/bubble_menu_padding"
@@ -52,7 +53,8 @@
android:id="@+id/bubble_manage_menu_dont_bubble_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="@dimen/bubble_menu_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
android:paddingStart="@dimen/bubble_menu_padding"
android:paddingEnd="@dimen/bubble_menu_padding"
@@ -78,7 +80,8 @@
android:id="@+id/bubble_manage_menu_settings_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="@dimen/bubble_menu_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
android:paddingStart="@dimen/bubble_menu_padding"
android:paddingEnd="@dimen/bubble_menu_padding"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
index 5c8c84c..ed00a87 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
@@ -21,8 +21,8 @@
android:layout_width="wrap_content"
android:paddingTop="48dp"
android:paddingBottom="48dp"
- android:paddingEnd="@dimen/bubble_user_education_padding_end"
- android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
+ android:paddingHorizontal="@dimen/bubble_user_education_padding_horizontal"
+ android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
diff --git a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
index b28f58f..4f6bdfd 100644
--- a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
@@ -23,8 +23,8 @@
android:clickable="true"
android:paddingTop="28dp"
android:paddingBottom="16dp"
- android:paddingEnd="@dimen/bubble_user_education_padding_end"
- android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
+ android:paddingEnd="@dimen/bubble_user_education_padding_horizontal"
+ android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 7e0c207..85bf2c1 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -21,8 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:orientation="horizontal"
- android:background="@drawable/desktop_mode_decor_title">
+ android:orientation="horizontal">
<LinearLayout
android:id="@+id/open_menu_button"
@@ -31,38 +30,36 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="8dp">
+ android:paddingStart="16dp">
<ImageView
android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_margin="4dp"
+ android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_height="@dimen/desktop_mode_caption_icon_radius"
android:layout_gravity="center_vertical"
android:contentDescription="@string/app_icon_text" />
<TextView
android:id="@+id/application_name"
android:layout_width="0dp"
- android:layout_height="match_parent"
+ android:layout_height="20dp"
android:minWidth="80dp"
- android:textColor="@color/desktop_mode_caption_app_name_dark"
+ android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
- android:gravity="center_vertical"
+ android:lineHeight="20dp"
+ android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
tools:text="Gmail"/>
<ImageButton
android:id="@+id/expand_menu_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:padding="4dp"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
android:contentDescription="@string/expand_menu_text"
android:src="@drawable/ic_baseline_expand_more_24"
- android:tint="@color/desktop_mode_caption_expand_button_dark"
android:background="@null"
android:scaleType="fitCenter"
android:clickable="false"
@@ -87,8 +84,7 @@
android:src="@drawable/decor_desktop_mode_maximize_button_dark"
android:scaleType="fitCenter"
android:gravity="end"
- android:background="@null"
- android:tint="@color/desktop_mode_caption_maximize_button_dark"/>
+ android:background="@null"/>
<ImageButton
android:id="@+id/close_window"
@@ -100,6 +96,5 @@
android:src="@drawable/decor_close_button_dark"
android:scaleType="fitCenter"
android:gravity="end"
- android:background="@null"
- android:tint="@color/desktop_mode_caption_close_button_dark"/>
+ android:background="@null"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index d93e9ba..cec7ee2 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -20,18 +20,16 @@
android:id="@+id/desktop_mode_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:background="@drawable/desktop_mode_decor_title">
+ android:gravity="center_horizontal">
<ImageButton
android:id="@+id/caption_handle"
android:layout_width="128dp"
- android:layout_height="42dp"
- android:paddingVertical="19dp"
+ android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
+ android:paddingVertical="16dp"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
android:scaleType="fitXY"
android:background="?android:selectableItemBackground"/>
-
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
new file mode 100644
index 0000000..c6f85a0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/app_info_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
+ android:layout_marginStart="1dp"
+ android:elevation="1dp"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_height="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:contentDescription="@string/app_icon_text"/>
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ tools:text="Gmail"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:lineHeight="20dp"
+ android:textStyle="normal"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/collapse_menu_button"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:padding="4dp"
+ android:layout_marginEnd="14dp"
+ android:layout_marginStart="14dp"
+ android:contentDescription="@string/collapse_menu_text"
+ android:src="@drawable/ic_baseline_expand_more_24"
+ android:rotation="180"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackgroundBorderless"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/windowing_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="horizontal"
+ android:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageButton
+ android:id="@+id/fullscreen_button"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/fullscreen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/split_screen_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/split_screen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/floating_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/float_button_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_floating"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/desktop_button"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/desktop_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/more_actions_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="vertical"
+ android:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background">
+
+ <Button
+ android:id="@+id/screenshot_button"
+ android:contentDescription="@string/screenshot_text"
+ android:text="@string/screenshot_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/select_button"
+ android:contentDescription="@string/select_text"
+ android:text="@string/select_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ </LinearLayout>
+</LinearLayout>
+
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
deleted file mode 100644
index 167a003..0000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="@dimen/desktop_mode_handle_menu_width"
- android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
- android:orientation="horizontal"
- android:background="@drawable/desktop_mode_decor_menu_background"
- android:gravity="center_vertical">
-
- <ImageView
- android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginStart="14dp"
- android:layout_marginEnd="14dp"
- android:contentDescription="@string/app_icon_text"/>
-
- <TextView
- android:id="@+id/application_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- tools:text="Gmail"
- android:textColor="@color/desktop_mode_caption_menu_text_color"
- android:textSize="14sp"
- android:textFontWeight="500"
- android:lineHeight="20dp"
- android:textStyle="normal"
- android:layout_weight="1"/>
-
- <ImageButton
- android:id="@+id/collapse_menu_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:padding="4dp"
- android:layout_marginEnd="14dp"
- android:layout_marginStart="14dp"
- android:contentDescription="@string/collapse_menu_text"
- android:src="@drawable/ic_baseline_expand_more_24"
- android:rotation="180"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:background="?android:selectableItemBackgroundBorderless"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
deleted file mode 100644
index 40a4b53..0000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/desktop_mode_handle_menu_width"
- android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
- android:orientation="vertical"
- android:background="@drawable/desktop_mode_decor_menu_background">
-
- <Button
- android:id="@+id/screenshot_button"
- android:contentDescription="@string/screenshot_text"
- android:text="@string/screenshot_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
- android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
- android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/close_button"
- android:contentDescription="@string/close_text"
- android:text="@string/close_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_close"
- android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
deleted file mode 100644
index 95283b9..0000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/desktop_mode_handle_menu_width"
- android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
- android:orientation="horizontal"
- android:background="@drawable/desktop_mode_decor_menu_background"
- android:gravity="center_vertical">
-
- <ImageButton
- android:id="@+id/fullscreen_button"
- android:layout_marginEnd="4dp"
- android:contentDescription="@string/fullscreen_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
- <ImageButton
- android:id="@+id/split_screen_button"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:contentDescription="@string/split_screen_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
- <ImageButton
- android:id="@+id/floating_button"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:contentDescription="@string/float_button_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_floating"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
- <ImageButton
- android:id="@+id/desktop_button"
- android:layout_marginStart="4dp"
- android:contentDescription="@string/desktop_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_active"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
new file mode 100644
index 0000000..0db72f7
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="@dimen/desktop_mode_maximize_menu_width"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_height"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:background="@drawable/desktop_mode_maximize_menu_background">
+
+
+ <Button
+ android:id="@+id/maximize_menu_maximize_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="120dp"
+ android:layout_height="80dp"
+ android:layout_marginRight="15dp"
+ android:color="@color/desktop_mode_maximize_menu_button"
+ android:background="@drawable/desktop_mode_maximize_menu_maximize_button_background"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:id="@+id/maximize_menu_snap_left_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="58dp"
+ android:layout_height="80dp"
+ android:layout_marginRight="6dp"
+ android:color="@color/desktop_mode_maximize_menu_button"
+ android:background="@drawable/desktop_mode_maximize_menu_snap_left_button_background"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:id="@+id/maximize_menu_snap_right_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="58dp"
+ android:layout_height="80dp"
+ android:color="@color/desktop_mode_maximize_menu_button"
+ android:background="@drawable/desktop_mode_maximize_menu_snap_right_button_background"
+ android:stateListAnimator="@null"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml b/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
deleted file mode 100644
index d732b01..0000000
--- a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-
-<com.android.wm.shell.legacysplitscreen.DividerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
-
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"
- android:background="@color/split_divider_background"/>
-
- <com.android.wm.shell.legacysplitscreen.MinimizedDockShadow
- style="@style/DockedDividerMinimizedShadow"
- android:id="@+id/minimized_dock_shadow"
- android:alpha="0"/>
-
- <com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
- android:id="@+id/docked_divider_handle"
- android:contentDescription="@string/accessibility_divider"
- android:background="@null"/>
-
-</com.android.wm.shell.legacysplitscreen.DividerView>
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index e3be700..db35c8c 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -24,17 +24,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"/>
-
<com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
android:contentDescription="@string/accessibility_divider"
android:background="@null"/>
<com.android.wm.shell.common.split.DividerRoundedCorner
+ android:id="@+id/docked_divider_rounded_corner"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index de4a225..90b13cd 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"App sal dalk nie met verdeelde skerm werk nie"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App steun nie verdeelde skerm nie"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Hierdie app kan net in 1 venster oopgemaak word"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Skermverdeler"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Beweeg na regs bo"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Beweeg na links onder"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Beweeg na regs onder"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"vou <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> uit"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"vou <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> in"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-instellings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Maak borrel toe"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Moenie dat gesprek \'n borrel word nie"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Het dit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen onlangse borrels nie"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Onlangse borrels en borrels wat toegemaak is, sal hier verskyn"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Klets met borrels"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nuwe gesprekke verskyn as ikone in ’n hoek onderaan jou skerm. Tik om hulle uit te vou, of sleep om hulle toe te maak."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Beheer borrels enige tyd"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te bestuur watter apps en gesprekke in borrels kan verskyn"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Borrel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Bestuur"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Borrel is toegemaak."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tik om hierdie program te herbegin vir ’n beter aansig."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tik om hierdie app te herbegin vir ’n beter aansig"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Verander hierdie app se aspekverhouding in Instellings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Verander aspekverhouding"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerakwessies?\nTik om aan te pas"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 21172e2..478585a 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -32,23 +32,23 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"መጠን ይቀይሩ"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="7429086980048964687">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"መተግበሪያ ከተከፈለ ማያ ገፅ ጋር ላይሠራ ይችላል"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
- <string name="accessibility_divider" msgid="6407584574218956849">"የተከፈለ የማያ ገጽ ከፋይ"</string>
- <string name="divider_title" msgid="1963391955593749442">"የተከፈለ የማያ ገጽ ከፋይ"</string>
- <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"የግራ ሙሉ ማያ ገጽ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"የተከፈለ የማያ ገፅ ከፋይ"</string>
+ <string name="divider_title" msgid="1963391955593749442">"የተከፈለ የማያ ገፅ ከፋይ"</string>
+ <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"የግራ ሙሉ ማያ ገፅ"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ግራ 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ግራ 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ግራ 30%"</string>
- <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገጽ"</string>
- <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገጽ"</string>
+ <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገፅ"</string>
+ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገፅ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ከላይ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ከላይ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ከላይ 30%"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገጽ"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገፅ"</string>
<string name="accessibility_split_left" msgid="1713683765575562458">"ወደ ግራ ከፋፍል"</string>
<string name="accessibility_split_right" msgid="8441001008181296837">"ወደ ቀኝ ከፋፍል"</string>
<string name="accessibility_split_top" msgid="2789329702027147146">"ወደ ላይ ከፋፍል"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ወደ ላይኛው ቀኝ አንቀሳቅስ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"የግርጌውን ግራ አንቀሳቅስ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ታችኛውን ቀኝ ያንቀሳቅሱ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ን ዘርጋ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ን ሰብስብ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"የ<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ቅንብሮች"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"አረፋን አሰናብት"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ውይይቶችን በአረፋ አታሳይ"</string>
@@ -76,16 +78,22 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ገባኝ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ምንም የቅርብ ጊዜ አረፋዎች የሉም"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"የቅርብ ጊዜ አረፋዎች እና የተሰናበቱ አረፋዎች እዚህ ብቅ ይላሉ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"አረፋዎችን በመጠቀም ይወያዩ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"አዲስ ውይይቶች በማያ ገፅዎ የታችኛው ጥግ ውስጥ እንደ አዶዎች ይታያሉ። ለመዘርጋት መታ ያድርጓቸው ወይም ለማሰናበት ይጎትቷቸው።"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"በማንኛውም ጊዜ ዓረፋዎችን ይቆጣጠሩ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"የትኛዎቹ መተግበሪያዎች እና ውይይቶች ዓረፋ መፍጠር እንደሚችሉ ለማስተዳደር እዚህ ጋር መታ ያድርጉ"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"አረፋ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ለተሻለ ዕይታ ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ።"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ለተሻለ ዕይታ ይህን መተግበሪያ እንደገና ለመጀመር መታ ያድርጉ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"የዚህን መተግበሪያ ምጥጥነ ገፅታ በቅንብሮች ውስጥ ይለውጡ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"የምጥጥነ ገፅታ ለውጥ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"የካሜራ ችግሮች አሉ?\nዳግም ለማበጀት መታ ያድርጉ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string>
- <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገፅ ሌላ መተግበሪያ ይጎትቱ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጭ ሁለቴ መታ ያድርጉ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ለተሻለ ዕይታ እንደገና ይጀመር?"</string>
@@ -102,11 +110,11 @@
<string name="app_icon_text" msgid="2823268023931811747">"የመተግበሪያ አዶ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
<string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገፅ"</string>
<string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
<string name="select_text" msgid="5139083974039906583">"ምረጥ"</string>
- <string name="screenshot_text" msgid="1477704010087786671">"ቅጽበታዊ ገጽ እይታ"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ቅጽበታዊ ገፅ ዕይታ"</string>
<string name="close_text" msgid="4986518933445178928">"ዝጋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
<string name="expand_menu_text" msgid="3847736164494181168">"ምናሌን ክፈት"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index a6be578..84c1c67 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -20,7 +20,7 @@
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ሥዕል-ላይ-ሥዕል"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string>
<string name="pip_close" msgid="2955969519031223530">"ዝጋ"</string>
- <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string>
+ <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገፅ"</string>
<string name="pip_move" msgid="158770205886688553">"ውሰድ"</string>
<string name="pip_expand" msgid="1051966011679297308">"ዘርጋ"</string>
<string name="pip_collapse" msgid="3903295106641385962">"ሰብስብ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index a714b49..b2a522c 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"قد لا يعمل التطبيق بشكل سليم في وضع تقسيم الشاشة."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"لا يعمل التطبيق في وضع تقسيم الشاشة."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"أداة تقسيم الشاشة"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"الانتقال إلى أعلى اليسار"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"نقل إلى أسفل يمين الشاشة"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نقل إلى أسفل اليسار"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"توسيع <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"تصغير <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"إعدادات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"إغلاق فقاعة المحادثة"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"عدم عرض المحادثة كفقاعة محادثة"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"حسنًا"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ليس هناك فقاعات محادثات"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ستظهر هنا أحدث فقاعات المحادثات وفقاعات المحادثات التي تم إغلاقها."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"الدردشة باستخدام فقاعات المحادثات"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"تظهر المحادثات الجديدة في شكل رموز بأسفل أحد جانبَي الشاشة. انقر على الرمز لتوسيع المحادثة أو اسحبه لإخفائها."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"التحكّم في إظهار الفقاعات في أي وقت"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"انقر هنا للتحكّم في إظهار فقاعات التطبيقات والمحادثات التي تريدها."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"فقاعة"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"انقر لإعادة تشغيل هذا التطبيق للحصول على عرض أفضل."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"انقر لإعادة تشغيل هذا التطبيق للحصول على تجربة عرض أفضل."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"يمكنك تغيير نسبة العرض إلى الارتفاع لهذا التطبيق من خلال \"الإعدادات\"."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغيير نسبة العرض إلى الارتفاع"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"هل هناك مشاكل في الكاميرا؟\nانقر لإعادة الضبط."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 6d86747..897c38f 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"এপ্টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"এপ্টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"এই এপ্টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"শীৰ্ষৰ সোঁফালে নিয়ক"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"বুটামটো বাওঁফালে নিয়ক"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"তলৰ সোঁফালে নিয়ক"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> সংকোচন কৰক"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ছেটিং"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল অগ্ৰাহ্য কৰক"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"বাৰ্তালাপ বাবল নকৰিব"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"বুজি পালোঁ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"কোনো শেহতীয়া bubbles নাই"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"শেহতীয়া bubbles আৰু অগ্ৰাহ্য কৰা bubbles ইয়াত প্ৰদর্শিত হ\'ব"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bubbles ব্যৱহাৰ কৰি চাট কৰক"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"নতুন বাৰ্তালাপসমূহ আপোনাৰ স্ক্ৰীনৰ একেবাৰে তলৰ এটা কোণত চিহ্ন হিচাপে দেখা পোৱা যায়। সেইসমূহ বিস্তাৰ কৰিবলৈ টিপক বা অগ্ৰাহ্য কৰিবলৈ টানি আনি এৰক।"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"যিকোনো সময়তে বাবল নিয়ন্ত্ৰণ কৰক"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"কোনবোৰ এপ্ আৰু বাৰ্তালাপ বাবল হ’ব পাৰে সেয়া পৰিচালনা কৰিবলৈ ইয়াত টিপক"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"উন্নত ভিউৰ বাবে এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক।"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"উন্নত ভিউ পোৱাৰ বাবে এপ্টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ছেটিঙলৈ গৈ এই এপ্টোৰ আকাৰৰ অনুপাত সলনি কৰক"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"আকাৰৰ অনুপাত সলনি কৰক"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"কেমেৰাৰ কোনো সমস্যা হৈছে নেকি?\nপুনৰ খাপ খোৱাবলৈ টিপক"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 7c66282..4854e0d 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Tətbiq bölünmüş ekranda işləməyə bilər"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Tətbiq bölünmüş ekranı dəstəkləmir"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Bu tətbiq yalnız 1 pəncərədə açıla bilər"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcısı"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Yuxarıya sağa köçürün"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Aşağıya sola köçürün"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Aşağıya sağa köçürün"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"genişləndirin: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"yığcamlaşdırın: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Yumrucuğu ləğv edin"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Söhbəti yumrucuqda göstərmə"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Anladım"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Yumrucuqlar yoxdur"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Son yumrucuqlar və buraxılmış yumrucuqlar burada görünəcək"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Yumrucuqlar vasitəsilə söhbət edin"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Yeni söhbətlər ekranın aşağı küncündə ikonalar kimi görünür. Toxunaraq genişləndirin, yaxud sürüşdürərək imtina edin."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Yumrucuqları idarə edin"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Bura toxunaraq yumrucuq göstərəcək tətbiq və söhbətləri idarə edin"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Qabarcıq"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"İdarə edin"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Qabarcıqdan imtina edilib."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toxunaraq bu tətbiqi yenidən başladın ki, daha görüntü əldə edəsiniz."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Yaxşı görünüş üçün toxunaraq bu tətbiqi yenidən başladın"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ayarlarda bu tətbiqin tərəflər nisbətini dəyişin"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tərəflər nisbətini dəyişin"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera problemi var?\nBərpa etmək üçün toxunun"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 8de9d11..cac4e67 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podeljeni ekran."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ova aplikacija može da se otvori samo u jednom prozoru"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Razdelnik podeljenog ekrana"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Premesti gore desno"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Premesti dole levo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premesti dole desno"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"proširite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skupite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Podešavanja za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne koristi oblačiće za konverzaciju"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Važi"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ovde se prikazuju nedavni i odbačeni oblačići"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Ćaskajte u oblačićima"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nove konverzacije se pojavljuju kao ikone u donjem uglu ekrana. Dodirnite da biste ih proširili ili prevucite da biste ih odbacili."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolišite oblačiće u svakom trenutku"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovde i odredite koje aplikacije i konverzacije mogu da imaju oblačić"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promenite razmeru ove aplikacije u Podešavanjima"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promeni razmeru"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Imate problema sa kamerom?\nDodirnite da biste ponovo uklopili"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim što možete da izgubite ono što ste uradili ili nesačuvane promene, ako ih ima"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, ali možete da izgubite napredak ili nesačuvane promene"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 3d99514..cac76df 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Праграма можа не працаваць у рэжыме падзеленага экрана"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Праграма не падтрымлівае рэжым падзеленага экрана"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Гэту праграму можна адкрыць толькі ў адным акне"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Раздзяляльнік падзеленага экрана"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Перамясціце правей і вышэй"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Перамясціць лявей і ніжэй"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перамясціць правей і ніжэй"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: разгарнуць"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: згарнуць"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Налады \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Адхіліць апавяшчэнне"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не паказваць размову ў выглядзе ўсплывальных апавяшчэнняў"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Зразумела"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Няма нядаўніх усплывальных апавяшчэнняў"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Нядаўнія і адхіленыя ўсплывальныя апавяшчэнні будуць паказаны тут"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Чат з выкарыстаннем усплывальных апавяшчэнняў"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Новыя размовы паказваюцца ў выглядзе значкоў у ніжнім вугле экрана. Націсніце на іх, каб разгарнуць. Перацягніце іх, калі хочаце закрыць."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Кіруйце наладамі ўсплывальных апавяшчэнняў у любы час"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Каб кіраваць усплывальнымі апавяшчэннямі для праграм і размоў, націсніце тут"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Націсніце, каб перазапусціць гэту праграму для лепшага прагляду."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Націсніце, каб перазапусціць гэту праграму для зручнейшага прагляду"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змяніць суадносіны бакоў для гэтай праграмы ў наладах"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змяніць суадносіны бакоў"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Праблемы з камерай?\nНацісніце, каб пераабсталяваць"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 0473f27..ac9a208 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Приложението може да не работи в режим на разделен екран"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложението не поддържа разделен екран"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Това приложение може да се отвори само в 1 прозорец"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Разделител в режима за разделен екран"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Преместване горе вдясно"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Преместване долу вляво"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Преместване долу вдясно"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"разгъване на <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"свиване на <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Настройки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отхвърляне на балончетата"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Без балончета за разговора"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Разбрах"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Няма скорошни балончета"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Скорошните и отхвърлените балончета ще се показват тук"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Чат с балончета"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Новите разговори се показват като икони в долния ъгъл на екрана ви. Докоснете, за да ги разгънете, или ги плъзнете за отхвърляне."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Управление на балончетата по всяко време"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Докоснете тук, за да управл. кои прил. и разговори могат да показват балончета"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Докоснете, за да рестартирате това приложение с цел по-добър изглед."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Докоснете, за да рестартирате това приложение с цел по-добър изглед"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Променете съотношението на това приложение в „Настройки“"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Промяна на съотношението"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблеми с камерата?\nДокоснете за ремонтиране"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 4fe1be0..3b83dcb 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"স্প্লিট স্ক্রিনে এই অ্যাপ নাও কাজ করতে পারে"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"স্প্লিট স্ক্রিনে এই অ্যাপ কাজ করে না"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"এই অ্যাপটি শুধুমাত্র ১টি উইন্ডোতে খোলা যেতে পারে"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্রিন বিভাজক"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"উপরে ডানদিকে সরান"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"নিচে বাঁদিকে সরান"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"নিচে ডান দিকে সরান"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> বড় করুন"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> আড়াল করুন"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> সেটিংস"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল খারিজ করুন"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"কথোপকথন বাবল হিসেবে দেখাবে না"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"বুঝেছি"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"কোনও সাম্প্রতিক বাবল নেই"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"সাম্প্রতিক ও বাতিল করা বাবল এখানে দেখা যাবে"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"বাবল ব্যবহার করে চ্যাট করুন"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"নতুন কথোপকথন, আপনার স্ক্রিনের নিচে কোণার দিকে আইকন হিসেবে দেখায়। সেটি বড় করতে ট্যাপ করুন বা বাতিল করতে টেনে আনুন।"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"যেকোনও সময় বাবল নিয়ন্ত্রণ করুন"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"কোন অ্যাপ ও কথোপকথনের জন্য বাবলের সুবিধা চান তা ম্যানেজ করতে এখানে ট্যাপ করুন"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন।"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"সেটিংস থেকে এই অ্যাপের অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ক্যামেরা সংক্রান্ত সমস্যা?\nরিফিট করতে ট্যাপ করুন"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index b39b497..813d163 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati na podijeljenom ekranu"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni ekran"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ova aplikacija se može otvoriti samo u 1 prozoru"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog ekrana"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Pomjerite gore desno"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Pomjeri dolje lijevo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pomjerite dolje desno"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"proširivanje oblačića <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sužavanje oblačića <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke aplikacije <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nemoj prikazivati razgovor u oblačićima"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Razumijem"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nedavni i odbačeni oblačići će se pojaviti ovdje"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatajte koristeći oblačiće"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novi razgovori se pojavljuju kao ikone u donjem uglu ekrana. Dodirnite da ih proširite ili prevucite da ih odbacite."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljajte oblačićima u svakom trenutku"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da upravljate time koje aplikacije i razgovori mogu imati oblačić"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijenite format slike aplikacije u Postavkama"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijenite format slike"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s kamerom?\nDodirnite da ponovo namjestite"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index fe76e73..d00c50b 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"És possible que l\'aplicació no funcioni amb la pantalla dividida"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'aplicació no admet la pantalla dividida"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aquesta aplicació només pot obrir-se en 1 finestra"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Separador de pantalla dividida"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mou a dalt a la dreta"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mou a baix a l\'esquerra"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mou a baix a la dreta"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"desplega <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"replega <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuració de l\'aplicació <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora la bombolla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostris la conversa com a bombolla"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entesos"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hi ha bombolles recents"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bombolles recents i les ignorades es mostraran aquí"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Xateja amb bombolles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Les converses noves es mostren com a icones en un extrem inferior de la pantalla. Toca per ampliar-les o arrossega per ignorar-les."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla les bombolles en qualsevol moment"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca aquí per gestionar quines aplicacions i converses poden fer servir bombolles"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bombolla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestiona"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"La bombolla s\'ha ignorat."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Canvia la relació d\'aspecte d\'aquesta aplicació a Configuració"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Canvia la relació d\'aspecte"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tens problemes amb la càmera?\nToca per resoldre\'ls"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 70e2970..40132e4 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikace v režimu rozdělené obrazovky nemusí fungovat"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikace nepodporuje režim rozdělené obrazovky"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Tuto aplikaci lze otevřít jen v jednom okně"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Čára rozdělující obrazovku"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Přesunout vpravo nahoru"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Přesunout vlevo dolů"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Přesunout vpravo dolů"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"rozbalit <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sbalit <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavení <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavřít bublinu"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovat konverzaci v bublinách"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Žádné nedávné bubliny"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Zde se budou zobrazovat nedávné bubliny a zavřené bubliny"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatujte pomocí bublin"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nové konverzace se zobrazí jako ikony v dolním rohu obrazovky. Klepnutím je rozbalíte, přetažením zavřete."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Nastavení bublin můžete kdykoli upravit"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Klepnutím sem lze spravovat, které aplikace a konverzace mohou vytvářet bubliny"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Změnit v Nastavení poměr stran této aplikace"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Změnit poměr stran"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s fotoaparátem?\nKlepnutím vyřešíte"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index c91cd7a..6e9738d 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Appen fungerer muligvis ikke i opdelt skærm"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen understøtter ikke opdelt skærm"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Denne app kan kun åbnes i 1 vindue"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Adskiller til opdelt skærm"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Flyt op til højre"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Flyt ned til venstre"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flyt ned til højre"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"udvid <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen seneste bobler"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nye bobler og afviste bobler vises her"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat ved hjælp af bobler"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nye samtaler vises som ikoner nederst på din skærm. Tryk for at udvide dem, eller træk for at lukke dem."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Administrer bobler når som helst"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tryk her for at administrere, hvilke apps og samtaler der kan vises i bobler"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen blev lukket."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tryk for at genstarte denne app, så visningen forbedres."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tryk for at genstarte denne app, så visningen forbedres"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Skift denne apps billedformat i Indstillinger"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Skift billedformat"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du problemer med dit kamera?\nTryk for at gendanne det oprindelige format"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 6ce475a..5da224d 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -20,7 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Schließen"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Maximieren"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"„Geteilter Bildschirm“ aktivieren"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Splitscreen aktivieren"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menü „Bild im Bild“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string>
@@ -32,9 +32,9 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Größe anpassen"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"In Stash legen"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
- <string name="dock_forced_resizable" msgid="7429086980048964687">"Die App funktioniert bei geteiltem Bildschirm unter Umständen nicht"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"„Geteilter Bildschirm“ wird in dieser App nicht unterstützt"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Die App funktioniert im Splitscreen-Modus unter Umständen nicht"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Splitscreen wird in dieser App nicht unterstützt"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Diese App kann nur in einem einzigen Fenster geöffnet werden"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Bildschirmteiler"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Nach rechts oben verschieben"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Nach unten links verschieben"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Nach unten rechts verschieben"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> maximieren"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> minimieren"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Einstellungen für <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubble schließen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Unterhaltung nicht als Bubble anzeigen"</string>
@@ -76,15 +78,21 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Keine kürzlich geschlossenen Bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Hier werden aktuelle und geschlossene Bubbles angezeigt"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bubbles zum Chatten verwenden"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Neue Unterhaltungen erscheinen als Symbole unten auf dem Display. Du kannst sie durch Antippen maximieren und durch Ziehen schließen."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubble-Einstellungen festlegen"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tippe hier, um zu verwalten, welche Apps und Unterhaltungen als Bubble angezeigt werden können"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Verwalten"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble verworfen."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tippe, um diese App neu zu starten und die Ansicht zu verbessern."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tippen, um diese App neu zu starten und die Ansicht zu verbessern"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Seitenverhältnis der App in den Einstellungen ändern"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Seitenverhältnis ändern"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Probleme mit der Kamera?\nZum Anpassen tippen."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string>
- <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Für Splitscreen-Modus weitere App hineinziehen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
@@ -102,13 +110,12 @@
<string name="app_icon_text" msgid="2823268023931811747">"App-Symbol"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
<string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Splitscreen"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
<string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
<string name="select_text" msgid="5139083974039906583">"Auswählen"</string>
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Schließen"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
- <!-- no translation found for expand_menu_text (3847736164494181168) -->
- <skip />
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menü öffnen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index ab5c44e..822b552 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Αυτή η εφαρμογή μπορεί να ανοίξει μόνο σε ένα παράθυρο"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Διαχωριστικό οθόνης"</string>
@@ -66,20 +66,28 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Μετακίνηση επάνω δεξιά"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Μετακίνηση κάτω αριστερά"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Μετακίνηση κάτω δεξιά"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ανάπτυξη <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"σύμπτυξη <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ρυθμίσεις <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Παράβλ. για συννεφ."</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Να μην γίνει προβολή της συζήτησης σε συννεφάκια."</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Συζητήστε χρησιμοποιώντας συννεφάκια."</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Οι νέες συζητήσεις εμφανίζονται ως κινούμενα εικονίδια ή συννεφάκια. Πατήστε για να ανοίξετε το συννεφάκι. Σύρετε για να το μετακινήσετε."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Ελέγξτε τα συννεφάκια ανά πάσα στιγμή."</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Πατήστε Διαχείριση για να απενεργοποιήσετε τα συννεφάκια από αυτήν την εφαρμογή."</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Πατήστε Διαχείριση για να απενεργοποιήσετε τα συννεφάκια από αυτή την εφαρμογή."</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Το κατάλαβα"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Δεν υπάρχουν πρόσφατα συννεφάκια"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Τα πρόσφατα συννεφάκια και τα συννεφάκια που παραβλέψατε θα εμφανίζονται εδώ."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Συζητήστε χρησιμοποιώντας συννεφάκια"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Οι νέες συζητήσεις εμφανίζονται ως εικονίδια σε μια από τις κάτω γωνίες της οθόνης. Πατήστε για να τις αναπτύξετε ή σύρετε για να τις παραβλέψετε."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ελέγξτε τα συννεφάκια ανά πάσα στιγμή."</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Πατήστε εδώ για τη διαχείριση εφαρμογών και συζητήσεων που προβάλλουν συννεφάκια"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Αλλάξτε τον λόγο διαστάσεων αυτής της εφαρμογής στις Ρυθμίσεις"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Αλλαγή λόγου διαστάσεων"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Προβλήματα με την κάμερα;\nΠατήστε για επιδιόρθωση."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index ea91014..76464b3 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in one window"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 01bdf95..c0c46cd 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in 1 window"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles anytime"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index ea91014..76464b3 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in one window"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index ea91014..76464b3 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in one window"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index f6dac79..f089938 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"App may not work with split screen"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App does not support split screen"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"This app can only be opened in 1 window"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles anytime"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 0190ea8..6bbc1e3 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Es posible que la app no funcione en el modo de pantalla dividida"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La app no es compatible con la función de pantalla dividida"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta app solo puede estar abierta en 1 ventana"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Ubicar arriba a la derecha"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Ubicar abajo a la izquierda"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ubicar abajo a la derecha"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expandir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Descartar burbuja"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar la conversación en burbuja"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hay burbujas recientes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Las burbujas recientes y las que se descartaron aparecerán aquí"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat con burbujas"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Las conversaciones nuevas aparecen como íconos en la esquina inferior de la pantalla. Presiona para expandirlas, o bien arrastra para descartarlas."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla las burbujas"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Presiona para administrar las apps y conversaciones que pueden mostrar burbujas"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Cuadro"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Se descartó el cuadro."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Presiona para reiniciar esta app y tener una mejor vista."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Presiona para reiniciar esta app y tener una mejor vista"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta app en Configuración"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Tienes problemas con la cámara?\nPresiona para reajustarla"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index ea44bea..c662ff6 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Puede que la aplicación no funcione con la pantalla dividida"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"La aplicación no es compatible con la pantalla dividida"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta aplicación solo puede abrirse en una ventana"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover arriba a la derecha"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover abajo a la izquierda."</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover abajo a la derecha"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"desplegar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ajustes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar conversación en burbuja"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hay burbujas recientes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Las burbujas recientes y las cerradas aparecerán aquí"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatea con burbujas"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Las nuevas conversaciones aparecen como iconos en una esquina inferior de la pantalla. Tócalas para ampliarlas o arrástralas para cerrarlas."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla las burbujas cuando quieras"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca aquí para gestionar qué aplicaciones y conversaciones pueden usar burbujas"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbuja"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toca para reiniciar esta aplicación y obtener una mejor vista."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Toca para reiniciar esta aplicación y obtener una mejor vista"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta aplicación en Ajustes"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Problemas con la cámara?\nToca para reajustar"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 90feff3..ade5e2d 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Rakendus ei pruugi jagatud ekraanikuvaga töötada."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Rakendus ei toeta jagatud ekraanikuva."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Selle rakenduse saab avada ainult ühes aknas"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Jagatud ekraanikuva jaotur"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Teisalda üles paremale"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Teisalda alla vasakule"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Teisalda alla paremale"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"laienda <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ahenda <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Rakenduse <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> seaded"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Sule mull"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ära kuva vestlust mullina"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Selge"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hiljutisi mulle pole"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Siin kuvatakse hiljutised ja suletud mullid."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Vestelge mullide abil"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Uued vestlused kuvatakse ekraanikuva alanurgas ikoonidena. Puudutage nende laiendamiseks või lohistage neist loobumiseks."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Juhtige mulle igal ajal"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Puudutage siin, et hallata, milliseid rakendusi ja vestlusi saab mullina kuvada"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Mull"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Halda"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Mullist loobuti."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muutke selle rakenduse kuvasuhet jaotises Seaded"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Muutke kuvasuhet"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kas teil on kaameraprobleeme?\nPuudutage ümberpaigutamiseks."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Kas taaskäivitada parema vaate saavutamiseks?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused."</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index de27a80..d6cb668 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -34,9 +34,9 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikazioak ez du onartzen pantaila zatitua"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Leiho bakar batean ireki daiteke aplikazioa"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
- <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
+ <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da exekutatu bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Pantaila-zatitzailea"</string>
<string name="divider_title" msgid="1963391955593749442">"Pantaila-zatitzailea"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ezarri ezkerraldea pantaila osoan"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Eraman goialdera, eskuinetara"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Eraman behealdera, ezkerretara"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Eraman behealdera, eskuinetara"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"zabaldu <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"tolestu <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> aplikazioaren ezarpenak"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baztertu burbuila"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ez erakutsi elkarrizketak burbuila gisa"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ados"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ez dago azkenaldiko burbuilarik"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Azken burbuilak eta baztertutakoak agertuko dira hemen"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Txateatu burbuilak erabilita"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Elkarrizketa berriak ikono gisa agertzen dira pantailaren beheko izkinan. Zabaltzeko, saka itzazu. Baztertzeko, aldiz, arrasta itzazu."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolatu burbuilak edonoiz"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Sakatu hau burbuiletan zein aplikazio eta elkarrizketa ager daitezkeen kudeatzeko"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbuila"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kudeatu"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Baztertu da globoa."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Hobeto ikusteko, sakatu hau aplikazioa berrabiarazteko."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Hobeto ikusteko, sakatu hau, eta aplikazioa berrabiarazi egingo da"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Aldatu aplikazioaren aspektu-erlazioa ezarpenetan"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Aldatu aspektu-erlazioa"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Arazoak dauzkazu kamerarekin?\nBerriro doitzeko, sakatu hau."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string>
@@ -102,7 +110,7 @@
<string name="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
<string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitzea"</string>
<string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
<string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
<string name="select_text" msgid="5139083974039906583">"Hautatu"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 13a2ea2..ba0f51c 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفیسازی"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن است برنامه با صفحهٔ دونیمه کار نکند"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"برنامه از صفحهٔ دونیمه پشتیبانی نمیکند"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره میتواند باز شود."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"این برنامه فقط در ۱ پنجره میتواند باز شود"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راهاندازی در نمایشگرهای ثانویه پشتیبانی نمیکند."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"تقسیمکننده صفحهٔ دونیمه"</string>
@@ -59,13 +59,15 @@
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یکدستی»"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابکهای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string>
- <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشت به پشته"</string>
+ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشتن به پشته"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> مورد بیشتر"</string>
<string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"انتقال به بالا سمت راست"</string>
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"انتقال به بالا سمت چپ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"انتقال به پایین سمت راست"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"انتقال به پایین سمت چپ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ازهم باز کردن <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"جمع کردن <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"تنظیمات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"رد کردن حبابک"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"مکالمه در حباب نشان داده نشود"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجهام"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"هیچ حبابک جدیدی وجود ندارد"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابکهای اخیر و حبابکهای ردشده اینجا ظاهر خواهند شد"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"گپ زدن بااستفاده از حبابک"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"مکالمههای جدید بهصورت نماد در گوشه پایین صفحهنمایش نشان داده میشود. برای ازهم بازکردن آنها ضربه بزنید یا برای بستن، آنها را بکشید."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کنترل حبابکها در هرزمانی"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"برای مدیریت اینکه کدام برنامهها و مکالمهها حباب داشته باشند، ضربه بزنید"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"حباب"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"نسبت ابعادی این برنامه را در «تنظیمات» تغییر دهید"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغییر نسبت ابعادی"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه ضربه بزنید"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 92fa760..a53f861 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Sovellus ei ehkä toimi jaetulla näytöllä"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Sovellus ei tue jaetun näytön tilaa"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Tämän sovelluksen voi avata vain yhdessä ikkunassa"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Näytönjakaja"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Siirrä oikeaan yläreunaan"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Siirrä vasempaan alareunaan"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Siirrä oikeaan alareunaan"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"laajenna <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"tiivistä <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: asetukset"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ohita kupla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Älä näytä kuplia keskusteluista"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Okei"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ei viimeaikaisia kuplia"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Viimeaikaiset ja äskettäin ohitetut kuplat näkyvät täällä"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chattaile kuplien avulla"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Uudet keskustelut näkyvät kuvakkeina näytön alareunassa. Laajenna ne napauttamalla tai hylkää ne vetämällä."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Muuta kuplien asetuksia milloin tahansa"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Valitse napauttamalla tästä, mitkä sovellukset ja keskustelut voivat kuplia"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Kupla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Ylläpidä"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Kupla ohitettu."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muuta tämän sovelluksen kuvasuhdetta Asetuksissa"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Vaihda kuvasuhdetta"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Onko kameran kanssa ongelmia?\nKorjaa napauttamalla"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 7814b7d..4563556 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"L\'application peut ne pas fonctionner avec l\'écran partagé"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'application ne prend pas en charge l\'écran partagé"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette application ne peut être ouverte que dans une seule fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer dans coin sup. droit"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer dans coin inf. gauche"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer dans coin inf. droit"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"développer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"réduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles récentes et les bulles ignorées s\'afficheront ici"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Clavarder en utilisant des bulles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Les nouvelles conversations apparaissent sous forme d\'icônes dans le coin inférieur de votre écran. Touchez-les icônes pour développer les conversations ou faites-les glisser pour les supprimer."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gérez les bulles en tout temps"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Touchez ici pour gérer les applis et les conversations à inclure aux bulles"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette application dans les paramètres"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index da5b5c9..8957571 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner en mode Écran partagé"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appli incompatible avec l\'écran partagé"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette appli ne peut être ouverte que dans 1 fenêtre"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer en haut à droite"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer en bas à gauche"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Développer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Réduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles récentes et ignorées s\'afficheront ici"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatter via des bulles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Les nouvelles conversations apparaissent sous forme d\'icônes dans l\'un des coins inférieurs de votre écran. Appuyez dessus pour les développer ou faites-les glisser pour les supprimer."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Contrôlez les bulles à tout moment"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Appuyez ici pour gérer les applis et conversations s\'affichant dans des bulles"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle fermée."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Appuyez pour redémarrer cette appli et avoir une meilleure vue."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Appuyez pour redémarrer cette appli et obtenir une meilleure vue."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Modifiez le format de cette appli dans les Paramètres."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier le format"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo ?\nAppuyez pour réajuster"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour améliorer l\'affichage ?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour en améliorer son aspect sur votre écran, mais vous risquez de perdre votre progression ou les modifications non enregistrées"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour un meilleur rendu sur votre écran, mais il se peut que vous perdiez votre progression ou les modifications non enregistrées"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index c08cff8..54c864e 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"É posible que a aplicación non funcione coa pantalla dividida"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A aplicación non admite a función de pantalla dividida"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta aplicación só se pode abrir en 1 ventá"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover á parte superior dereita"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover á parte infer. esquerda"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover á parte inferior dereita"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"despregar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar burbulla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mostrar a conversa como burbulla"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Non hai burbullas recentes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"As burbullas recentes e ignoradas aparecerán aquí."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatea usando burbullas"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"As conversas novas móstranse como iconas nunha das esquinas inferiores da pantalla. Tócaas para amplialas ou arrástraas para pechalas."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlar as burbullas"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca para xestionar as aplicacións e conversas que poden aparecer en burbullas"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbulla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Xestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ignorouse a burbulla."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia a proporción desta aplicación en Configuración"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar a proporción"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tes problemas coa cámara?\nToca para reaxustala"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 2a52199..2b09279 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"વિભાજિત સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ઍપ વિભાજિત સ્ક્રીનને સપોર્ટ કરતી નથી"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ઉપર જમણે ખસેડો"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"નીચે ડાબે ખસેડો"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"નીચે જમણે ખસેડો"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> મોટું કરો"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> નાનું કરો"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> સેટિંગ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"બબલને છોડી દો"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"વાતચીતને બબલ કરશો નહીં"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"સમજાઈ ગયું"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"તાજેતરના કોઈ બબલ નથી"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"એકદમ નવા બબલ અને છોડી દીધેલા બબલ અહીં દેખાશે"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"બબલનો ઉપયોગ કરીને ચૅટ કરો"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"નવી વાતચીતો તમારી સ્ક્રીનના નીચેના ખૂણામાં આઇકન તરીકે દેખાય છે. તેમને મોટી કરવા માટે, તેમના પર ટૅપ કરો અથવા તેમને છોડી દેવા માટે, તેમને ખેંચો."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"બબલને કોઈપણ સમયે નિયંત્રિત કરે છે"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"કઈ ઍપ અને વાતચીતોને બબલ કરવા માગો છો તે મેનેજ કરવા માટે, અહીં ટૅપ કરો"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"બબલ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"સેટિંગમાં આ ઍપનો સાપેક્ષ ગુણોત્તર બદલો"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"સાપેક્ષ ગુણોત્તર બદલો"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"કૅમેરામાં સમસ્યાઓ છે?\nફરીથી ફિટ કરવા માટે ટૅપ કરો"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index fb5040b..35b099a 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"मुमकिन है कि ऐप्लिकेशन, स्प्लिट स्क्रीन मोड में काम न करे"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यह ऐप्लिकेशन, स्प्लिट स्क्रीन मोड पर काम नहीं करता"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन डिवाइडर मोड"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"सबसे ऊपर दाईं ओर ले जाएं"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"बाईं ओर सबसे नीचे ले जाएं"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"सबसे नीचे दाईं ओर ले जाएं"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> को बड़ा करें"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> को छोटा करें"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> की सेटिंग"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारिज करें"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"बातचीत को बबल न करें"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ठीक है"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के कोई बबल्स नहीं हैं"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल्स का इस्तेमाल करके चैट करें"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नई बातचीत, आपकी स्क्रीन पर सबसे नीचे आइकॉन के तौर पर दिखती हैं. किसी आइकॉन को बड़ा करने के लिए उस पर टैप करें या खारिज करने के लिए उसे खींचें और छोड़ें."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स की सुविधा को कंट्रोल करें"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"किसी ऐप्लिकेशन और बातचीत के लिए बबल की सुविधा को मैनेज करने के लिए यहां टैप करें"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"टैप करके ऐप्लिकेशन को रीस्टार्ट करें और बेहतर व्यू पाएं."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"बेहतर व्यू पाने के लिए, टैप करके ऐप्लिकेशन को रीस्टार्ट करें"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिंग में जाकर इस ऐप्लिकेशन का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्या कैमरे से जुड़ी कोई समस्या है?\nफिर से फ़िट करने के लिए टैप करें"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"बेहतर व्यू पाने के लिए ऐप्लिकेशन को रीस्टार्ट करना है?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, आपने जो बदलाव सेव नहीं किए हैं या अब तक जो काम किए हैं उनका डेटा, ऐप्लिकेशन रीस्टार्ट करने पर मिट सकता है"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, इससे अब तक किया गया काम और सेव न किए गए बदलाव मिट सकते हैं"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करें"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 2535657..f2c3c22 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podržava podijeljeni zaslon"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ova se aplikacija može otvoriti samo u jednom prozoru"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog zaslona"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Premjesti u gornji desni kut"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Premjesti u donji lijevi kut"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premjestite u donji desni kut"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"proširite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sažmite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Zaustavi razgovor u oblačićima"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Shvaćam"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ovdje će se prikazivati nedavni i odbačeni oblačići"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Oblačići u chatu"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novi se razgovori prikazuju kao ikone u donjem kutu zaslona. Dodirnite da biste ih proširili ili ih povucite da biste ih odbacili."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljanje oblačićima u svakom trenutku"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da biste odredili koje aplikacije i razgovori mogu imati oblačić"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijeni omjer slike ove aplikacije u postavkama"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijeni omjer slike"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s fotoaparatom?\nDodirnite za popravak"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 7566439..d94bb29 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Az alkalmazás nem támogatja az osztott képernyőt"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ez az alkalmazás csak egy ablakban nyitható meg"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Elválasztó az osztott képernyős nézetben"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Áthelyezés fel és jobbra"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Áthelyezés le és balra"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Áthelyezés le és jobbra"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> kibontása"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> összecsukása"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> beállításai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Buborék elvetése"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne jelenjen meg a beszélgetés buborékban"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Értem"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nincsenek buborékok a közelmúltból"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"A legutóbbi és az elvetett buborékok itt jelennek majd meg"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Buborékokat használó csevegés"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Az új beszélgetések ikonokként jelennek meg a képernyő alsó sarkában. Koppintással kibonthatja, húzással pedig elvetheti őket."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Buborékok vezérlése bármikor"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ide koppintva jeleníthetők meg az alkalmazások és a beszélgetések buborékként"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Buborék"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kezelés"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Buborék elvetve."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Az app méretarányát a Beállításokban módosíthatja"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Méretarány módosítása"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerával kapcsolatos problémába ütközött?\nKoppintson a megoldáshoz."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 2b20870..f2945c1 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Հավելվածը չի աջակցում էկրանի տրոհումը"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Տրոհված էկրանի բաժանիչ"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Տեղափոխել վերև՝ աջ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Տեղափոխել ներքև՝ ձախ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Տեղափոխել ներքև՝ աջ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>. ծավալել"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>. ծալել"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – կարգավորումներ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Փակել ամպիկը"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Զրույցը չցուցադրել ամպիկի տեսքով"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Եղավ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ամպիկներ չկան"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Այստեղ կցուցադրվեն վերջերս օգտագործված և փակված ամպիկները, որոնք կկարողանաք հեշտությամբ վերաբացել"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Զրույցի ամպիկներ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Նոր զրույցները հայտնվում են որպես պատկերակներ էկրանի ներքևի անկյունում։ Հպեք՝ դրանք ծավալելու, կամ քաշեք՝ մերժելու համար։"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ամպիկների կարգավորումներ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Հպեք այստեղ՝ ընտրելու, թե որ հավելվածների և զրույցների համար ամպիկներ ցուցադրել"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Պղպջակ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար։"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Փոխել հավելվածի կողմերի հարաբերակցությունը Կարգավորումներում"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Փոխել չափերի հարաբերակցությունը"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Տեսախցիկի հետ կապված խնդիրնե՞ր կան։\nՀպեք՝ վերակարգավորելու համար։"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 5747deb..c39b429 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikasi mungkin tidak berfungsi dengan layar terpisah"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikasi tidak mendukung layar terpisah"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aplikasi ini hanya dapat dibuka di 1 jendela"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Pembagi layar terpisah"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Pindahkan ke kanan atas"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Pindahkan ke kiri bawah"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pindahkan ke kanan bawah"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"luaskan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ciutkan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setelan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Tutup balon"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan gunakan percakapan balon"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Oke"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Tidak ada balon baru-baru ini"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Balon yang baru dipakai dan balon yang telah ditutup akan muncul di sini"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat dalam tampilan balon"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Percakapan baru muncul sebagai ikon di bagian pojok bawah layar. Ketuk untuk meluaskan percakapan atau tarik untuk menutup percakapan."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrol balon kapan saja"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ketuk di sini untuk mengelola balon aplikasi dan percakapan"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kelola"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon ditutup."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ubah rasio aspek aplikasi ini di Setelan"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ubah rasio aspek"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Masalah kamera?\nKetuk untuk memperbaiki"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string>
@@ -88,7 +96,7 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
- <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk tampilan yang lebih baik?"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk melihat tampilan yang lebih baik?"</string>
<string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 145d26d..630eaa3 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Forritið virkar hugsanlega ekki með skjáskiptingu"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Forritið styður ekki skjáskiptingu"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aðeins er hægt að opna þetta forrit í 1 glugga"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Skilrúm skjáskiptingar"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Færa efst til hægri"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Færa neðst til vinstri"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Færðu neðst til hægri"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"stækka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"minnka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Stillingar <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Loka blöðru"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ekki setja samtal í blöðru"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ég skil"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Engar nýlegar blöðrur"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nýlegar blöðrur og blöðrur sem þú hefur lokað birtast hér"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Spjall með blöðrum"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Ný samtöl birtast sem tákn neðst á horni skjásins. Ýttu til að stækka þau eða dragðu til að hunsa þau."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Hægt er að stjórna blöðrum hvenær sem er"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ýttu hér til að stjórna því hvaða forrit og samtöl mega nota blöðrur."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Blaðra"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Ýta til að endurræsa forritið og fá betri sýn."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Ýttu til að endurræsa forritið og fá betri sýn"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Breyta myndhlutfalli þessa forrits í stillingunum"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Breyta myndhlutfalli"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Myndavélavesen?\nÝttu til að breyta stærð"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 025646c..77893c9 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"L\'app potrebbe non funzionare con lo schermo diviso"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'app non supporta la modalità schermo diviso"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Questa app può essere aperta soltanto in 1 finestra"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Strumento per schermo diviso"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Sposta in alto a destra"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Sposta in basso a sinistra"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sposta in basso a destra"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"espandi <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"comprimi <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Impostazioni <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora bolla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mettere la conversazione nella bolla"</string>
@@ -76,17 +78,23 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nessuna bolla recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Le bolle recenti e ignorate appariranno qui"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta utilizzando le bolle"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Le nuove conversazioni vengono visualizzate sotto forma di icone in un angolo inferiore dello schermo. Tocca per espanderle o trascina per chiuderle."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gestisci le bolle in qualsiasi momento"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tocca qui per gestire le app e le conversazioni per cui mostrare le bolle"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Fumetto ignorato."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tocca per riavviare quest\'app per una migliore visualizzazione."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tocca per riavviare l\'app e migliorare la visualizzazione"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia le proporzioni dell\'app nelle Impostazioni"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambia proporzioni"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi con la fotocamera?\nTocca per risolverli"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trascina in un\'altra app per usare lo schermo diviso"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string>
- <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vuoi riavviare per migliorare la visualizzazione?"</string>
<string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puoi riavviare l\'app affinché venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index bb3845b..4f28c23 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"יכול להיות שהאפליקציה לא תפעל עם מסך מפוצל"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"האפליקציה לא תומכת במסך מפוצל"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"מחלק מסך מפוצל"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"העברה לפינה הימנית העליונה"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"העברה לפינה השמאלית התחתונה"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"העברה לפינה הימנית התחתונה"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"הרחבה של <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"כיווץ של <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"הגדרות <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"סגירת בועה"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"אין להציג בועות לשיחה"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"הבנתי"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"אין בועות מהזמן האחרון"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"בועות אחרונות ובועות שנסגרו יופיעו כאן"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"צ\'אט בבועות"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"שיחות חדשות מופיעות כסמלים בפינה התחתונה של המסך. אפשר להקיש כדי להרחיב אותן או לגרור כדי לסגור אותן."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"שליטה בבועות בכל זמן"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"אפשר להקיש כאן כדי לקבוע אילו אפליקציות ושיחות יוכלו להופיע בבועות"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"אפשר לשנות את יחס הגובה-רוחב של האפליקציה הזו ב\'הגדרות\'"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"שינוי יחס גובה-רוחב"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע התאמה מחדש"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 6c1bafe..60b4d7e 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"アプリは分割画面では動作しないことがあります"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"アプリで分割画面がサポートされていません"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"このアプリはウィンドウが 1 つの場合のみ開くことができます"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"分割画面の分割線"</string>
@@ -54,7 +54,7 @@
<string name="accessibility_split_top" msgid="2789329702027147146">"上に分割"</string>
<string name="accessibility_split_bottom" msgid="8694551025220868191">"下に分割"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"片手モードの使用"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの任意の場所をタップします"</string>
+ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの上側の任意の場所をタップします"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"片手モードを開始します"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"片手モードを終了します"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> のバブルの設定"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"右上に移動"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"左下に移動"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"右下に移動"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>を開きます"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>を閉じます"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> の設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"バブルを閉じる"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"会話をバブルで表示しない"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近閉じたバブルはありません"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近表示されたバブルや閉じたバブルが、ここに表示されます"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"チャットでバブルを使う"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"新しい会話がアイコンとして画面下部に表示されます。タップすると開き、ドラッグして閉じることができます。"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"バブルはいつでも管理可能"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"バブルで表示するアプリや会話を管理するには、ここをタップします"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"タップしてこのアプリを再起動すると、表示が適切になります。"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"タップしてこのアプリを再起動すると、表示が適切になります"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"このアプリのアスペクト比を [設定] で変更します"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"アスペクト比を変更"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"カメラに関する問題の場合は、\nタップすると修正できます"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index e58e67a..28d2257 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"ეკრანის გაყოფის გამყოფი"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"გადაანაცვლეთ ზევით და მარჯვნივ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ქვევით და მარცხნივ გადატანა"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"გადაანაცვ. ქვემოთ და მარჯვნივ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-ის გაფართოება"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-ის ჩაკეცვა"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-ის პარამეტრები"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ბუშტის დახურვა"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"აიკრძალოს საუბრის ბუშტები"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"გასაგებია"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ბოლო დროს გამოყენებული ბუშტები არ არის"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"აქ გამოჩნდება ბოლოდროინდელი ბუშტები და უარყოფილი ბუშტები"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ჩეთი ბუშტების გამოყენებით"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ახალი საუბრები ხატულას სახით გამოჩნდება თქვენი ეკრანის ქვედა კუთხეში. შეეხეთ გასაფართოებლად და ჩაავლეთ მათ დასახურად."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ამოხტომის გაკონტროლება ნებისმიერ დროს"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"აქ შეეხეთ იმის სამართავად, თუ რომელი აპები და საუბრები ამოხტეს"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ბუშტი"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"შეეხეთ, რომ გადატვირთოთ ეს აპი უკეთესი ხედისთვის."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"შეხებით გადატვირთეთ ეს აპი უკეთესი ხედის მისაღებად"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"შეცვალეთ ამ აპის თანაფარდობა პარამეტრებიდან"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"თანაფარდობის შეცვლა"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"კამერად პრობლემები აქვს?\nშეეხეთ გამოსასწორებლად"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 7c9120e..441df8d7 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Қолданбада экранды бөлу мүмкін емес."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлу режимінің бөлгіші"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жоғары оң жаққа жылжыту"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төменгі сол жаққа жылжыту"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төменгі оң жаққа жылжыту"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: жаю"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: жию"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлері"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Қалқымалы хабарды жабу"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Әңгіменің қалқыма хабары көрсетілмесін"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түсінікті"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Жақындағы қалқыма хабарлар жоқ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Соңғы және жабылған қалқыма хабарлар осы жерде көрсетіледі."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Қалқыма хабарлар арқылы чатта сөйлесу"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Жаңа әңгімелер экранның төменгі бөлігінде белгіше түрінде көрсетіледі. Оларды жаю үшін түртіңіз, ал жабу үшін сүйреңіз."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Қалқыма хабарларды кез келген уақытта басқарыңыз"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Қалқыма хабарда көрсетілетін қолданбалар мен әңгімелерді реттеу үшін осы жерді түртіңіз."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Ыңғайлы көріністі реттеу үшін қолданбаны түртіп, өшіріп қосыңыз."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Көріністі жақсарту үшін осы қолданбаны түртіп, қайта ашыңыз."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Осы қолданбаның арақатынасын параметрлерден өзгертуге болады."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Арақатынасты өзгерту"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада қателер шықты ма?\nЖөндеу үшін түртіңіз."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 4530267..efa6418 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"កម្មវិធីអាចមិនដំណើរការជាមួយមុខងារបំបែកអេក្រង់ទេ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"កម្មវិធីមិនអាចប្រើមុខងារបំបែកអេក្រង់បានទេ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"អាចបើកកម្មវិធីនេះបានតែក្នុងវិនដូ 1 ប៉ុណ្ណោះ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះប្រហែលជាមិនដំណើរការនៅលើអេក្រង់បន្ទាប់បន្សំទេ។"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធីនេះមិនអាចចាប់ផ្តើមនៅលើអេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារបំបែកអេក្រង់"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ផ្លាស់ទីទៅផ្នែកខាងលើខាងស្ដាំ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ផ្លាស់ទីទៅផ្នែកខាងក្រោមខាងឆ្វេង"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ផ្លាស់ទីទៅផ្នែកខាងក្រោមខាងស្ដាំ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ពង្រីក <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"បង្រួម <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ការកំណត់ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ច្រានចោលពពុះ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"កុំបង្ហាញការសន្ទនាជាពពុះ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"យល់ហើយ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"មិនមានពពុះថ្មីៗទេ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ពពុះថ្មីៗ និងពពុះដែលបានបិទនឹងបង្ហាញនៅទីនេះ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ជជែកដោយប្រើផ្ទាំងអណ្ដែត"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ការសន្ទនាថ្មីៗបង្ហាញជារូបតំណាងនៅជ្រុងខាងក្រោមនៃអេក្រង់របស់អ្នក។ សូមចុច ដើម្បីពង្រីកការសន្ទនាទាំងនោះ ឬអូស ដើម្បីច្រានចោល។"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"គ្រប់គ្រងផ្ទាំងអណ្ដែតនៅពេលណាក៏បាន"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ចុចត្រង់នេះ ដើម្បីគ្រប់គ្រងកម្មវិធី និងការសន្ទនាដែលអាចបង្ហាញជាផ្ទាំងអណ្ដែត"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ពពុះ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោលសារលេចឡើង។"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ។"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ចុចដើម្បីចាប់ផ្ដើមកម្មវិធីនេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ផ្លាស់ប្ដូរសមាមាត្ររបស់កម្មវិធីនេះនៅក្នុងការកំណត់"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ប្ដូរសមាមាត្រ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"មានបញ្ហាពាក់ព័ន្ធនឹងកាមេរ៉ាឬ?\nចុចដើម្បីដោះស្រាយ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបានដោះស្រាយបញ្ហានេះទេឬ?\nចុចដើម្បីត្រឡប់"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមានបញ្ហាពាក់ព័ន្ធនឹងកាមេរ៉ាទេឬ? ចុចដើម្បីច្រានចោល។"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 2dfbad4..0cda445 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ಬಲ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ಸ್ಕ್ರೀನ್ನ ಎಡ ಕೆಳಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಿ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ಬಬಲ್ ವಜಾಗೊಳಿಸಿ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ಸಂಭಾಷಣೆಯನ್ನು ಬಬಲ್ ಮಾಡಬೇಡಿ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ಅರ್ಥವಾಯಿತು"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಬಬಲ್ಸ್ ಇಲ್ಲ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ಇತ್ತೀಚಿನ ಬಬಲ್ಸ್ ಮತ್ತು ವಜಾಗೊಳಿಸಿದ ಬಬಲ್ಸ್ ಇಲ್ಲಿ ಗೋಚರಿಸುತ್ತವೆ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ಬಬಲ್ಸ್ ಬಳಸಿ ಚಾಟ್ ಮಾಡಿ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ಹೊಸ ಸಂಭಾಷಣೆಗಳು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಕೆಳಗಿನ ಮೂಲೆಯಲ್ಲಿ ಐಕಾನ್ಗಳಾಗಿ ಗೋಚರಿಸುತ್ತವೆ. ವಿಸ್ತರಿಸಲು ಅವುಗಳನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ ಅಥವಾ ವಜಾಗೊಳಿಸಲು ಡ್ರ್ಯಾಗ್ ಮಾಡಿ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ಬಬಲ್ಸ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ಯಾವ ಆ್ಯಪ್ಗಳು ಮತ್ತು ಸಂಭಾಷಣೆಗಳನ್ನು ಬಬಲ್ ಮಾಡಬಹುದು ಎಂಬುದನ್ನು ನಿರ್ವಹಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ಬಬಲ್"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಈ ಆ್ಯಪ್ನ ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿವೆಯೇ?\nಮರುಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 39d717d..676506f 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"앱이 화면 분할 모드로는 작동하지 않을 수 있습니다"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"앱이 화면 분할을 지원하지 않습니다"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"이 앱은 창 1개에서만 열 수 있습니다."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"화면 분할기"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"오른쪽 상단으로 이동"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"왼쪽 하단으로 이동"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"오른쪽 하단으로 이동"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> 펼치기"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> 접기"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> 설정"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"대화창 닫기"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"대화를 대화창으로 표시하지 않기"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"확인"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"최근 대화창 없음"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"최근 대화창과 내가 닫은 대화창이 여기에 표시됩니다."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"대화창으로 채팅하기"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"새 대화는 화면 하단에 아이콘으로 표시됩니다. 아이콘을 탭하여 펼치거나 드래그하여 닫습니다."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"언제든지 대화창을 제어하세요"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"대화창을 만들 수 있는 앱과 대화를 관리하려면 여기를 탭하세요."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"보기를 개선하려면 탭하여 앱을 다시 시작합니다."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"탭하면 앱을 다시 시작하여 보기를 개선합니다."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"설정에서 앱의 가로세로 비율을 변경합니다."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"가로세로 비율 변경"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"카메라 문제가 있나요?\n해결하려면 탭하세요."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index f210ea2..57253ef 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Сүрөт ичиндеги сүрөт менюсу"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичиндеги сүрөт"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колдонмосу бул функцияны пайдаланбасын десеңиз, жөндөөлөрдү ачып туруп, аны өчүрүп коюңуз."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колдонмосу бул функцияны пайдаланбасын десеңиз, параметрлерди ачып туруп, аны өчүрүп коюңуз."</string>
<string name="pip_play" msgid="3496151081459417097">"Ойнотуу"</string>
<string name="pip_pause" msgid="690688849510295232">"Тындыруу"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Кийинкисине өткөрүп жиберүү"</string>
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Колдонмодо экран бөлүнбөшү мүмкүн"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Колдонмодо экран бөлүнбөйт"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Бул колдонмону 1 терезеде гана ачууга болот"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлгүч"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жогорку оң жакка жылдыруу"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төмөнкү сол жакка жылдыруу"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> жайып көрсөтүү"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> жыйыштыруу"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Азырынча эч нерсе жок"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Акыркы жана жабылган калкып чыкма билдирмелер ушул жерде көрүнөт"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Калкып чыкма билдирмелер аркылуу маектешүү"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Жаңы сүйлөшүүлөр экраныңыздын төмөнкү бурчунда сүрөтчөлөр катары көрүнөт. Аларды жайып көрсөтүү үчүн таптап же четке кагуу үчүн сүйрөңүз."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Калкып чыкма билдирме түрүндө көрүнө турган колдонмолор менен маектерди тандоо үчүн бул жерди таптаңыз"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Көбүк"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Бул колдонмонун тараптарынын катнашын параметрлерден өзгөртүү"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Тараптардын катнашын өзгөртүү"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада маселелер келип чыктыбы?\nОңдоо үчүн таптаңыз"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string>
diff --git a/libs/WindowManager/Shell/res/values-land/dimens.xml b/libs/WindowManager/Shell/res/values-land/dimens.xml
index a95323f..1b96fa2 100644
--- a/libs/WindowManager/Shell/res/values-land/dimens.xml
+++ b/libs/WindowManager/Shell/res/values-land/dimens.xml
@@ -16,13 +16,6 @@
*/
-->
<resources>
- <!-- Divider handle size for legacy split screen -->
- <dimen name="docked_divider_handle_width">2dp</dimen>
- <dimen name="docked_divider_handle_height">16dp</dimen>
- <!-- Divider handle size for split screen -->
- <dimen name="split_divider_handle_width">3dp</dimen>
- <dimen name="split_divider_handle_height">72dp</dimen>
-
<!-- Padding between status bar and bubbles when displayed in expanded state, smaller
value in landscape since we have limited vertical space-->
<dimen name="bubble_padding_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
deleted file mode 100644
index e89f65b..0000000
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="DockedDividerBackground">
- <item name="android:layout_width">@dimen/split_divider_bar_width</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:layout_gravity">center_horizontal</item>
- <item name="android:background">@color/split_divider_background</item>
- </style>
-
- <style name="DockedDividerHandle">
- <item name="android:layout_gravity">center</item>
- <item name="android:layout_width">48dp</item>
- <item name="android:layout_height">96dp</item>
- </style>
-
- <style name="DockedDividerMinimizedShadow">
- <item name="android:layout_width">8dp</item>
- <item name="android:layout_height">match_parent</item>
- </style>
-</resources>
-
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index a25699f..c5f6e22 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບໂໝດແບ່ງໜ້າຈໍ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ແອັບບໍ່ຮອງຮັບການແບ່ງໜ້າຈໍ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"ເສັ້ນແບ່ງໜ້າຈໍ"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ຍ້າຍຂວາເທິງ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ຍ້າຍຊ້າຍລຸ່ມ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ຍ້າຍຂວາລຸ່ມ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ຂະຫຍາຍ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ຫຍໍ້ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ລົງ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ການຕັ້ງຄ່າ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ປິດຟອງໄວ້"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ຢ່າໃຊ້ຟອງໃນການສົນທະນາ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ເຂົ້າໃຈແລ້ວ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ບໍ່ມີຟອງຫຼ້າສຸດ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ຟອງຫຼ້າສຸດ ແລະ ຟອງທີ່ປິດໄປຈະປາກົດຢູ່ບ່ອນນີ້"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ສົນທະນາໂດຍໃຊ້ຟອງ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ການສົນທະນາໃໝ່ໆຈະປາກົດເປັນໄອຄອນຢູ່ມຸມລຸ່ມສຸດຂອງໜ້າຈໍຂອງທ່ານ. ແຕະເພື່ອຂະຫຍາຍ ຫຼື ລາກເພື່ອປິດໄວ້."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ຄວບຄຸມຟອງໄດ້ທຸກເວລາ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ແຕະບ່ອນນີ້ເພື່ອຈັດການແອັບ ແລະ ການສົນທະນາທີ່ສາມາດສະແດງເປັນແບບຟອງໄດ້"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ຟອງ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ຈັດການ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ປິດ Bubble ໄສ້ແລ້ວ."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ປ່ຽນອັດຕາສ່ວນຂອງແອັບນີ້ໃນການຕັ້ງຄ່າ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ປ່ຽນອັດຕາສ່ວນ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ?\nແຕະເພື່ອປັບໃໝ່"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອປິດໄວ້."</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index d893fcf..eeed5a4 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Programa gali neveikti naudojant išskaidyto ekrano režimą"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programoje nepalaikomas išskaidyto ekrano režimas"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Šią programą galima atidaryti tik viename lange"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Išskaidyto ekrano režimo daliklis"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Perkelti į viršų dešinėje"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Perkelti į apačią kairėje"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Perkelti į apačią dešinėje"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"išskleisti „<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>“"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sutraukti „<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>“"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ nustatymai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Atsisakyti burbulo"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerodyti pokalbio burbule"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Supratau"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nėra naujausių burbulų"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Naujausi ir atsisakyti burbulai bus rodomi čia"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Pokalbis naudojant burbulus"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nauji pokalbiai rodomi kaip piktogramos apatiniame ekrano kampe. Palieskite piktogramą, jei norite išplėsti pokalbį, arba nuvilkite, kad atsisakytumėte."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bet kada valdyti burbulus"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Palietę čia valdykite, kurie pokalbiai ir programos gali būti rodomi burbuluose"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Debesėlis"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Tvarkyti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Debesėlio atsisakyta."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Pakeiskite šios programos kraštinių santykį"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Keisti kraštinių santykį"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Iškilo problemų dėl kameros?\nPalieskite, kad pritaikytumėte iš naujo"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index a1fbcdd..4324d468 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Lietotnē netiek atbalstīta ekrāna sadalīšana"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Šo lietotni var atvērt tikai vienā logā"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Ekrāna sadalītājs"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Pārvietot augšpusē pa labi"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Pārvietot apakšpusē pa kreisi"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pārvietot apakšpusē pa labi"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Izvērst “<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>”"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Sakļaut “<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>”"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Lietotnes <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iestatījumi"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Nerādīt burbuli"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerādīt sarunu burbuļos"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Labi"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nav nesen aizvērtu burbuļu"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Šeit būs redzami nesen rādītie burbuļi un aizvērtie burbuļi"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Tērzēšana, izmantojot burbuļus"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Jaunas sarunas tiek parādītas kā ikonas jūsu ekrāna apakšējā stūrī. Varat pieskarties, lai tās izvērstu, vai vilkt, lai tās noraidītu."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Pārvaldīt burbuļus jebkurā laikā"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Pieskarieties šeit, lai pārvaldītu, kuras lietotnes un sarunas var rādīt burbulī"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbulis"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pārvaldīt"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbulis ir noraidīts."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Iestatījumos mainiet šīs lietotnes malu attiecību."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mainīt malu attiecību"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Vai ir problēmas ar kameru?\nPieskarieties, lai tās novērstu."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 427433c..471f5bd 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Апликацијата можеби нема да работи со поделен екран"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликацијата не поддржува поделен екран"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Апликацијава може да се отвори само во еден прозорец"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Разделник на поделен екран"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Премести горе десно"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Премести долу лево"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести долу десно"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"прошири <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"собери <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Поставки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отфрли балонче"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не прикажувај го разговорот во балончиња"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Сфатив"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Нема неодамнешни балончиња"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Неодамнешните и отфрлените балончиња ќе се појавуваат тука"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Разговор со балончиња"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Новите разговори се појавуваат како икони во долниот агол од екранот. Допрете за да ги проширите или повлечете за да ги отфрлите."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Контролирајте ги балончињата во секое време"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Допрете тука за да одредите на кои апл. и разговори може да се појават балончиња"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Допрете за да ја рестартирате апликацијава за подобар приказ."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Допрете за да ја рестартирате апликацијава за подобар приказ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Промени го соодносот на апликацијава во „Поставки“"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Променување на соодносот"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми со камерата?\nДопрете за да се совпадне повторно"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 5cca248..5bc694a 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"സ്ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"സ്ക്രീൻ വിഭജന മോഡിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"സ്ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"മുകളിൽ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ചുവടെ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ചുരുക്കുക"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ക്രമീകരണം"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ബബിൾ ഡിസ്മിസ് ചെയ്യൂ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"സംഭാഷണം ബബിൾ ചെയ്യരുത്"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"മനസ്സിലായി"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"അടുത്തിടെയുള്ള ബബിളുകൾ ഒന്നുമില്ല"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"അടുത്തിടെയുള്ള ബബിളുകൾ, ഡിസ്മിസ് ചെയ്ത ബബിളുകൾ എന്നിവ ഇവിടെ ദൃശ്യമാവും"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ബബിളുകൾ ഉപയോഗിച്ച് ചാറ്റ് ചെയ്യുക"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"പുതിയ സംഭാഷണങ്ങൾ, നിങ്ങളുടെ സ്ക്രീനിന്റെ താഴെ മൂലയിൽ ഐക്കണുകളായി ദൃശ്യമാകും. വികസിപ്പിക്കാൻ അവയിൽ ടാപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ഡിസ്മിസ് ചെയ്യാൻ വലിച്ചിടുക."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ബബിളുകൾ ഏതുസമയത്തും നിയന്ത്രിക്കുക"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ഏതൊക്കെ ആപ്പുകളും സംഭാഷണങ്ങളും ബബിൾ ചെയ്യാനാകുമെന്നത് മാനേജ് ചെയ്യാൻ ഇവിടെ ടാപ്പ് ചെയ്യുക"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"മികച്ച കാഴ്ചയ്ക്കായി ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"മികച്ച കാഴ്ചയ്ക്കായി ഈ ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ഈ ആപ്പിന്റെ വീക്ഷണ അനുപാതം, ക്രമീകരണത്തിൽ മാറ്റുക"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"വീക്ഷണ അനുപാതം മാറ്റുക"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ക്യാമറ പ്രശ്നങ്ങളുണ്ടോ?\nശരിയാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 72e54fc..0268c64 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Апп дэлгэцийг хуваах горимтой ажиллахгүй байж магадгүй"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апп дэлгэцийг хуваах горимыг дэмждэггүй"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Дэлгэцийг хуваах хуваагч"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Баруун дээш зөөх"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Зүүн доош зөөх"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Баруун доош зөөх"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-г дэлгэх"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-г хураах"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-н тохиргоо"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Бөмбөлгийг хаах"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Харилцан яриаг бүү бөмбөлөг болго"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ойлголоо"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Саяхны бөмбөлөг алга байна"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Саяхны бөмбөлгүүд болон үл хэрэгссэн бөмбөлгүүд энд харагдана"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Бөмбөлгүүд ашиглан чатлах"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Шинэ харилцан ярианууд таны дэлгэцийн доод буланд дүрс тэмдэг байдлаар харагдана. Тэдгээрийг дэлгэхийн тулд товших эсвэл хаахын тулд чирнэ үү."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Бөмбөлгүүдийг хүссэн үедээ хянах"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ямар апп болон харилцан ярианууд бөмбөлгөөр харагдахыг энд удирдана уу"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Бөмбөлөг"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Энэ аппын харьцааг Тохиргоонд өөрчилнө үү"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Харьцааг өөрчлөх"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерын асуудал гарсан уу?\nДахин тааруулахын тулд товшино уу"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index a9e6319a..2e6163e 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"अॅप कदाचित स्प्लिट स्क्रीनसह काम करणार नाही"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"अॅप हे स्प्लिट स्क्रीनला सपोर्ट करत नाही"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"हे अॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अॅप कदाचित चालणार नाही."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अॅप लाँच होणार नाही."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन विभाजक"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"वर उजवीकडे हलवा"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"तळाशी डावीकडे हलवा"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"तळाशी उजवीकडे हलवा"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> विस्तार करा"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> कोलॅप्स करा"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> सेटिंग्ज"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल डिसमिस करा"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"संभाषणाला बबल करू नका"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"समजले"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"अलीकडील कोणतेही बबल नाहीत"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"अलीकडील बबल आणि डिसमिस केलेले बबल येथे दिसतील"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल वापरून चॅट करा"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नवीन संभाषणे ही तुमच्या स्क्रीनच्या तळाशी असलेल्या कोपऱ्यात आयकनच्या स्वरूपात दिसतात. त्यांचा विस्तार करण्यासाठी टॅप करा किंवा ती डिसमिस करण्यासाठी ड्रॅग करा."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"बबल कधीही नियंत्रित करा"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"कोणती ॲप्स आणि संभाषणे बबल होऊ शकतात हे व्यवस्थापित करण्यासाठी येथे टॅप करा"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"अधिक चांगल्या व्ह्यूसाठी हे अॅप रीस्टार्ट करण्याकरिता टॅप करा."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"अधिक चांगल्या दृश्यासाठी हे अॅप रीस्टार्ट करण्याकरिता टॅप करा"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिंग्ज मध्ये या ॲपचा आस्पेक्ट रेशो बदला"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"आस्पेक्ट रेशो बदला"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"कॅमेराशी संबंधित काही समस्या आहेत का?\nपुन्हा फिट करण्यासाठी टॅप करा"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्यासाठी टॅप करा."</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index b475317..a60e61b 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Apl mungkin tidak berfungsi dengan skrin pisah"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Apl tidak menyokong skrin pisah"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Apl ini hanya boleh dibuka dalam 1 tetingkap"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Pembahagi skrin pisah"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Alihkan ke atas sebelah kanan"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Alihkan ke bawah sebelah kiri"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Alihkan ke bawah sebelah kanan"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"kembangkan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"kuncupkan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Tetapan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ketepikan gelembung"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan jadikan perbualan dalam bentuk gelembung"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Tiada gelembung terbaharu"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Gelembung baharu dan gelembung yang diketepikan akan dipaparkan di sini"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bersembang menggunakan gelembung"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Perbualan baharu dipaparkan sebagai ikon pada penjuru sebelah bawah skrin anda. Ketik untuk mengembangkan perbualan atau seret untuk mengetepikan perbualan."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kawal gelembung pada bila-bila masa"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ketik di sini untuk mengurus apl dan perbualan yang boleh menggunakan gelembung"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Gelembung"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Urus"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Gelembung diketepikan."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Tukar nisbah bidang apl ini dalam Tetapan"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tukar nisbah bidang"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Isu kamera?\nKetik untuk memuatkan semula"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index cb6a1df..6b91d46 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"အက်ပ်တွင် မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို မပံ့ပိုးပါ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ညာဘက်ထိပ်သို့ ရွှေ့ပါ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ဘယ်အောက်ခြေသို့ ရွှေ့ရန်"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ညာအောက်ခြေသို့ ရွှေ့ပါ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ကို ချဲ့ရန်"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ကို ချုံ့ရန်"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ဆက်တင်များ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ပူဖောင်းကွက် ပယ်ရန်"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"စကားဝိုင်းကို ပူဖောင်းကွက် မပြုလုပ်ပါနှင့်"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"နားလည်ပြီ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"လတ်တလော ပူဖောင်းကွက်များ မရှိပါ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"လတ်တလော ပူဖောင်းကွက်များနှင့် ပိတ်လိုက်သော ပူဖောင်းကွက်များကို ဤနေရာတွင် မြင်ရပါမည်"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ပူဖောင်းကွက်သုံး၍ ချတ်လုပ်ခြင်း"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"စကားဝိုင်းအသစ်များကို သင့်ဖန်သားပြင် အောက်ခြေထောင့်တွင် သင်္ကေတများအဖြစ် မြင်ရပါမည်။ ၎င်းတို့ကို ဖြန့်ရန်တို့ပါ (သို့) ပယ်ရန်ဖိဆွဲပါ။"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ပူဖောင်းကွက်သုံးနိုင်သည့် အက်ပ်နှင့် စကားဝိုင်းများ စီမံရန် ဤနေရာကို တို့ပါ"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့နိုင်သည်။"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့ပါ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ဆက်တင်များတွင် ဤအက်ပ်၏အချိုးအစားကို ပြောင်းရန်"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"အချိုးစား ပြောင်းရန်"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ကင်မရာပြဿနာလား။\nပြင်ဆင်ရန် တို့ပါ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 6c80144..ec9ece3 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Det kan hende at appen ikke fungerer med delt skjerm"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen støtter ikke delt skjerm"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Denne appen kan bare åpnes i ett vindu"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Skilleelement for delt skjerm"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Flytt til øverst til høyre"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Flytt til nederst til venstre"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytt til nederst til høyre"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"vis <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-innstillinger"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Lukk boblen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nylige bobler og avviste bobler vises her"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat med bobler"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nye samtaler vises som ikoner i et hjørne nede på skjermen. Trykk for å åpne dem, eller dra for å lukke dem."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontroller bobler når som helst"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Trykk her for å administrere hvilke apper og samtaler som kan vises i bobler"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen er avvist."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Trykk for å starte denne appen på nytt for bedre visning."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Trykk for å starte denne appen på nytt og få en bedre visning"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Endre høyde/bredde-forholdet for denne appen i innstillingene"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Endre høyde/bredde-forholdet"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du kameraproblemer?\nTrykk for å tilpasse"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index f9f5805..8bb07be 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"यो एपले स्प्लिट स्क्रिन मोडमा काम नगर्न सक्छ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यो एप स्प्लिट स्क्रिन मोडमा प्रयोग गर्न मिल्दैन"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रिन डिभाइडर"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"सिरानमा दायाँतिर सार्नुहोस्"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"पुछारमा बायाँतिर सार्नुहोस्"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"पुछारमा दायाँतिर सार्नुहोस्"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> का सेटिङहरू"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारेज गर्नुहोस्"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"वार्तालाप बबलको रूपमा नदेखाइयोस्"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"बुझेँ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हालैका बबलहरू छैनन्"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हालैका बबल र खारेज गरिएका बबलहरू यहाँ देखिने छन्"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल प्रयोग गरी कुराकानी गर्नुहोस्"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नयाँ वार्तालापहरू तपाईंको डिभाइसको स्क्रिनको पुछारको कुनामा आइकनका रूपमा देखिन्छन्। ती आइकन ठुलो बनाउन ट्याप गर्नुहोस् वा ती आइकन हटाउन ड्र्याग गर्नुहोस्।"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जुनसुकै बेला बबलसम्बन्धी सुविधा नियन्त्रण गर्नुहोस्"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"कुन एप र कुराकानी बबल प्रयोग गर्न सक्छन् भन्ने कुराको व्यवस्थापन गर्न यहाँ ट्याप गर्नुहोस्"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"यो एप अझ राम्रो हेर्न मिल्ने बनाउनका लागि यसलाई रिस्टार्ट गर्न ट्याप गर्नुहोस्।"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"अझ राम्रो भ्यू प्राप्त गर्नका लागि यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस्"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"सेटिङमा गई यो एपको एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्यामेरासम्बन्धी समस्या देखियो?\nसमस्या हल गर्न ट्याप गर्नुहोस्"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 3064ccc..c6c60ae 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"De app werkt misschien niet met gesplitst scherm"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"App ondersteunt geen gesplitst scherm"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Deze app kan maar in 1 venster worden geopend"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Scheiding voor gesplitst scherm"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Naar rechtsboven verplaatsen"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Naar linksonder verplaatsen"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> uitvouwen"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> samenvouwen"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen recente bubbels"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatten met bubbels"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nieuwe gesprekken verschijnen als iconen in een benedenhoek van je scherm. Tik om ze uit te vouwen of sleep om ze te sluiten."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren wanneer je wilt"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te beheren welke apps en gesprekken als bubbel kunnen worden getoond"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tik om deze app opnieuw op te starten voor een betere weergave."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tik om deze app opnieuw op te starten voor een betere weergave"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Wijzig de beeldverhouding van deze app in Instellingen"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Beeldverhouding wijzigen"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Cameraproblemen?\nTik om opnieuw passend te maken."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index e4c7053..927dde4 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -26,7 +26,7 @@
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"ଛବି-ଭିତରେ-ଛବି\"ରେ ଅଛି"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ଏହି ବୈଶିଷ୍ଟ୍ୟ <xliff:g id="NAME">%s</xliff:g> ବ୍ୟବହାର ନକରିବାକୁ ଯଦି ଆପଣ ଚାହାଁନ୍ତି, ସେଟିଙ୍ଗ ଖୋଲିବାକୁ ଟାପ୍ କରନ୍ତୁ ଏବଂ ଏହା ଅଫ୍ କରିଦିଅନ୍ତୁ।"</string>
<string name="pip_play" msgid="3496151081459417097">"ପ୍ଲେ କରନ୍ତୁ"</string>
- <string name="pip_pause" msgid="690688849510295232">"ପଜ୍ କରନ୍ତୁ"</string>
+ <string name="pip_pause" msgid="690688849510295232">"ବିରତ କରନ୍ତୁ"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"ପରବର୍ତ୍ତୀକୁ ଯାଆନ୍ତୁ"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"ପୂର୍ବବର୍ତ୍ତୀକୁ ଛାଡ଼ନ୍ତୁ"</string>
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ରିସାଇଜ୍ କରନ୍ତୁ"</string>
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରେ ଆପ କାମ କରିନପାରେ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ଆପ ସମର୍ଥନ କରେ ନାହିଁ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ କାମ ନକରିପାରେ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ଉପର-ଡାହାଣକୁ ନିଅନ୍ତୁ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ତଳ ବାମକୁ ନିଅନ୍ତୁ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ତଳ ଡାହାଣକୁ ନିଅନ୍ତୁ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ବିସ୍ତାର କରନ୍ତୁ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ସେଟିଂସ୍"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ବବଲ୍ ଖାରଜ କରନ୍ତୁ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ବାର୍ତ୍ତାଳାପକୁ ବବଲ୍ କରନ୍ତୁ ନାହିଁ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ବୁଝିଗଲି"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ବର୍ତ୍ତମାନ କୌଣସି ବବଲ୍ ନାହିଁ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ବର୍ତ୍ତମାନର ଏବଂ ଖାରଜ କରାଯାଇଥିବା ବବଲଗୁଡ଼ିକ ଏଠାରେ ଦେଖାଯିବ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ବବଲଗୁଡ଼ିକୁ ବ୍ୟବହାର କରି ଚାଟ୍ କରନ୍ତୁ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ନୂଆ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଆପଣଙ୍କ ସ୍କ୍ରିନର ଏକ ନିମ୍ନ କୋଣରେ ଆଇକନଗୁଡ଼ିକ ଭାବେ ଦେଖାଯାଏ। ସେଗୁଡ଼ିକୁ ବିସ୍ତାର କରିବା ପାଇଁ ଟାପ କରନ୍ତୁ କିମ୍ବା ସେଗୁଡ଼ିକୁ ଖାରଜ କରିବା ପାଇଁ ଡ୍ରାଗ କରନ୍ତୁ।"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ଯେ କୌଣସି ସମୟରେ ବବଲଗୁଡ଼ିକ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"କେଉଁ ଆପ୍ସ ଓ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ବବଲ ହୋଇପାରିବ ତାହା ପରିଚାଳନା କରିବାକୁ ଏଠାରେ ଟାପ କରନ୍ତୁ"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ବବଲ୍"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ସେଟିଂସରେ ଏହି ଆପର ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"କ୍ୟାମେରାରେ ସମସ୍ୟା ଅଛି?\nପୁଣି ଫିଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index d9f7f34..0e12fb8 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ਉੱਪਰ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ਹੇਠਾਂ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ਸੈਟਿੰਗਾਂ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕਰੋ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ਗੱਲਬਾਤ \'ਤੇ ਬਬਲ ਨਾ ਲਾਓ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ਸਮਝ ਲਿਆ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ਕੋਈ ਹਾਲੀਆ ਬਬਲ ਨਹੀਂ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ਹਾਲੀਆ ਬਬਲ ਅਤੇ ਖਾਰਜ ਕੀਤੇ ਬਬਲ ਇੱਥੇ ਦਿਸਣਗੇ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ਬਬਲ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਚੈਟ ਕਰੋ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ਨਵੀਂਆਂ ਗੱਲਾਂਬਾਤਾਂ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਲੇ ਕੋਨੇ ਵਿੱਚ ਪ੍ਰਤੀਕਾਂ ਦੇ ਰੂਪ ਵਿੱਚ ਦਿਖਦੀਆਂ ਹਨ। ਉਨ੍ਹਾਂ ਦਾ ਵਿਸਤਾਰ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ਜਾਂ ਉਨ੍ਹਾਂ ਨੂੰ ਖਾਰਜ ਕਰਨ ਲਈ ਘਸੀਟੋ।"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ਬਬਲ ਦੀ ਸੁਵਿਧਾ ਨੂੰ ਕਿਸੇ ਵੀ ਵੇਲੇ ਕੰਟਰੋਲ ਕਰੋ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ਇਹ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਇੱਥੇ ਟੈਪ ਕਰੋ ਕਿ ਕਿਹੜੀਆਂ ਐਪਾਂ ਅਤੇ ਗੱਲਾਂਬਾਤਾਂ ਬਬਲ ਹੋ ਸਕਦੀਆਂ ਹਨ"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ਬੁਲਬੁਲਾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਵਾਸਤੇ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਇਸ ਐਪ ਦੇ ਆਕਾਰ ਅਨੁਪਾਤ ਨੂੰ ਬਦਲੋ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ?\nਮੁੜ-ਫਿੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 0699f5d..75a8ce6 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacja może nie działać przy podzielonym ekranie"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacja nie obsługuje podzielonego ekranu"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Linia dzielenia ekranu"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Przenieś w prawy górny róg"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Przenieś w lewy dolny róg"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Przenieś w prawy dolny róg"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"rozwiń dymek <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"zwiń dymek <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – ustawienia"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zamknij dymek"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nie wyświetlaj rozmowy jako dymka"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Brak ostatnich dymków"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tutaj będą pojawiać się ostatnie i odrzucone dymki"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Czatuj, korzystając z dymków"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nowe rozmowy pojawiają się jako ikony w dolnym rogu ekranu. Kliknij, aby je rozwinąć, lub przeciągnij, aby je zamknąć."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Zarządzaj dymkami, kiedy chcesz"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Kliknij tutaj, aby zarządzać wyświetlaniem aplikacji i rozmów jako dymków"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Dymek"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Zarządzaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Zamknięto dymek"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Kliknij, aby zrestartować aplikację i zyskać lepszą widoczność."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Kliknij w celu zrestartowania aplikacji, aby lepiej się wyświetlała."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmień proporcje obrazu aplikacji w Ustawieniach"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmień proporcje obrazu"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemy z aparatem?\nKliknij, aby dopasować"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index eea9be2..b84a0de 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à divisão de tela"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esse app só pode ser aberto em uma única janela"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover para canto superior direito"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover para canto inferior esquerdo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"abrir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"fechar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e dispensados aparecerão aqui"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Converse usando balões"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novas conversas aparecem como ícones no canto inferior da tela. Toque neles para abrir ou arraste para dispensar."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balões a qualquer momento"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerenciar quais apps e conversas podem aparecer em balões"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas Configurações"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index ed0cdb6..d84bfcd 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu de ecrã no ecrã"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A app <xliff:g id="NAME">%s</xliff:g> está no modo de ecrã no ecrã"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"Se não pretende que a app <xliff:g id="NAME">%s</xliff:g> utilize esta funcionalidade, toque para abrir as definições e desative-a."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"Se não quer que a app <xliff:g id="NAME">%s</xliff:g> utilize esta funcionalidade, toque para abrir as definições e desative-a."</string>
<string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string>
<string name="pip_pause" msgid="690688849510295232">"Pausar"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Mudar para o seguinte"</string>
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"A app pode não funcionar com o ecrã dividido"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"A app não é compatível com o ecrã dividido"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esta app só pode ser aberta em 1 janela"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divisor do ecrã dividido"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover parte superior direita"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover p/ parte infer. esquerda"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover parte inferior direita"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expandir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"reduzir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Definições de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar balão"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não apresentar a conversa em balões"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e ignorados vão aparecer aqui."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Converse no chat através de balões"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"As novas conversas aparecem como ícones no canto inferior do ecrã. Toque para as expandir ou arraste para as ignorar."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balões em qualquer altura"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerir que apps e conversas podem aparecer em balões"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar esta app e ficar com uma melhor visão"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Altere o formato desta app nas Definições"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Altere o formato"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index eea9be2..b84a0de 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"É possível que o app não funcione com a tela dividida"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"O app não oferece suporte à divisão de tela"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Esse app só pode ser aberto em uma única janela"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover para canto superior direito"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover para canto inferior esquerdo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"abrir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"fechar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e dispensados aparecerão aqui"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Converse usando balões"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novas conversas aparecem como ícones no canto inferior da tela. Toque neles para abrir ou arraste para dispensar."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balões a qualquer momento"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerenciar quais apps e conversas podem aparecer em balões"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas Configurações"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 8a64b16..eeea428 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplicația nu acceptă ecranul împărțit"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Aplicația se poate deschide într-o singură fereastră"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Separator pentru ecranul împărțit"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mută în dreapta sus"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mută în stânga jos"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"extinde <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"restrânge <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nu există baloane recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Baloanele recente și baloanele respinse vor apărea aici"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat cu baloane"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Conversațiile noi apar ca pictograme în colțul de jos al ecranului. Atinge pentru a le extinde sau trage pentru a le închide."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlează baloanele oricând"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Atinge aici pentru a gestiona aplicațiile și conversațiile care pot apărea în balon"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare mai bună."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Atinge ca să repornești aplicația pentru o vizualizare mai bună"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Schimbă raportul de dimensiuni al aplicației din Setări"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Schimbă raportul de dimensiuni"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ai probleme cu camera foto?\nAtinge pentru a reîncadra"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index a7db44d..26b0f94 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Когда включено разделение экрана, приложение может работать нестабильно."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложение не поддерживает разделение экрана."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Это приложение можно открыть только в одном окне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Разделитель экрана"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Перенести в правый верхний угол"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Перенести в левый нижний угол"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перенести в правый нижний угол"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Развернуть <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Свернуть <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: настройки"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Скрыть всплывающий чат"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не показывать всплывающий чат для разговора"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ОК"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Нет недавних всплывающих чатов"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Здесь будут появляться недавние и скрытые всплывающие чаты."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Всплывающие чаты"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Новые чаты появляются в виде значков в нижней части экрана. Коснитесь их, чтобы развернуть. Перетащите их, если хотите закрыть."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Всплывающие чаты"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Укажите приложения и разговоры, для которых разрешены всплывающие чаты."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Всплывающая подсказка"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Нажмите, чтобы перезапустить приложение и настроить удобный для просмотра вид"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Нажмите, чтобы перезапустить приложение и оптимизировать размер"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Изменить соотношение сторон приложения в настройках"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Изменить соотношение сторон"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблемы с камерой?\nНажмите, чтобы исправить."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 4153ce2..9b9a430 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"යෙදුම බෙදීම් තිරය සමග ක්රියා නොකළ හැක"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"යෙදුම බෙදුම් තිරයට සහාය නොදක්වයි"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්රියා නොකළ හැකිය."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"බෙදුම් තිර වෙන්කරණය"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ඉහළ දකුණට ගෙන යන්න"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"පහළ වමට ගෙන යන්න"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"පහළ දකුණට ගෙන යන්න"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> දිග හරින්න"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> හකුළන්න"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> සැකසීම්"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"බුබුලු ඉවත ලන්න"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"සංවාදය බුබුලු නොදමන්න"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"තේරුණා"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"මෑත බුබුලු නැත"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"මෑත බුබුලු සහ ඉවත ලූ බුබුලු මෙහි දිස් වනු ඇත"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"බුබුලු භාවිතයෙන් කතාබහ කරන්න"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"නව සංවාද ඔබේ තිරයෙහි පහළ කෙළවරේ නිරූපක ලෙස දිස් වේ. ඒවා පුළුල් කිරීමට තට්ටු කරන්න හෝ ඒවා ඉවත දැමීමට අදින්න."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ඕනෑම වේලාවක බුබුලු පාලනය කරන්න"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"බුබුලු කළ හැකි යෙදුම් සහ සංවාද කළමනාකරණය කිරීමට මෙහි තට්ටු කරන්න"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"බුබුළු"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"වඩා හොඳ දසුනක් ලබා ගැනීම සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"වඩා හොඳ දසුනක් සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"සැකසීම් තුළ මෙම යෙදුමේ දර්ශන අනුපාතය වෙනස් කරන්න"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"දර්ශන අනුපාතය වෙනස් කරන්න"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"කැමරා ගැටලුද?\nයළි සවි කිරීමට තට්ටු කරන්න"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්රතිවර්තනය කිරීමට තට්ටු කරන්න"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4e38943..4b21531 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikácia nemusí fungovať s rozdelenou obrazovkou"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikácia nepodporuje rozdelenú obrazovku"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Táto aplikácia môže byť otvorená iba v jednom okne"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Rozdeľovač obrazovky"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Presunúť doprava nahor"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Presunúť doľava nadol"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Presunúť doprava nadol"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"rozbaliť <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"zbaliť <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavenia aplikácie <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavrieť bublinu"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovať konverzáciu ako bublinu"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Dobre"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Žiadne nedávne bubliny"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tu sa budú zobrazovať nedávne a zavreté bubliny"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Čet pomocou bublín"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nové konverzácie sa zobrazujú ako ikony v dolnom rohu obrazovky. Klepnutím ich rozbalíte a presunutím zavriete."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ovládajte bubliny kedykoľvek"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Klepnite tu a spravujte, ktoré aplikácie a konverzácie môžu ovládať bubliny"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovať"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina bola zavretá."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmeniť pomer strán tejto aplikácie v Nastaveniach"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmeniť pomer strán"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s kamerou?\nKlepnutím znova upravte."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index b0e67a7..581cf5b 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"To aplikacijo je mogoče odpreti samo v enem oknu"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Razdelilnik zaslonov"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Premakni zgoraj desno"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Premakni spodaj levo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premakni spodaj desno"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"razširitev oblačka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"strnitev oblačka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavitve za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Opusti oblaček"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Pogovora ne prikaži v oblačku"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"V redu"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ni nedavnih oblačkov"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tukaj bodo prikazani tako nedavni kot tudi opuščeni oblački"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Klepet z oblački"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novi pogovori so prikazani kot ikone v enem od spodnjih kotov zaslona. Z dotikom pogovore razširite, z vlečenjem jih opustite."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljanje oblačkov"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dotaknite se tukaj za upravljanje aplikacij in pogovorov, ki so lahko prikazani v oblačkih"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Mehurček"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblaček je bil opuščen."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Razmerje stranic te aplikacije spremenite v nastavitvah."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Sprememba razmerja stranic"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Težave s fotoaparatom?\nDotaknite se za vnovično prilagoditev"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 29bfb92..9dc7b7e 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Aplikacioni mund të mos funksionojë me ekranin e ndarë"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikacioni nuk mbështet ekranin e ndarë"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ky aplikacion mund të hapet vetëm në 1 dritare"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Ndarësi i ekranit të ndarë"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Lëviz lart djathtas"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Zhvendos poshtë majtas"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Lëvize poshtë djathtas"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"zgjero <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"palos <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cilësimet e <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Hiqe flluskën"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Mos e vendos bisedën në flluskë"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"E kuptova"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nuk ka flluska të fundit"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Flluskat e fundit dhe flluskat e hequra do të shfaqen këtu"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bisedo duke përdorur flluskat"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Bisedat e reja shfaqen si ikona në këndin e poshtëm të ekranit tënd. Trokit për t\'i zgjeruar ose zvarrit për t\'i hequr ato."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrollo flluskat në çdo moment"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Trokit këtu për të menaxhuar aplikacionet e bisedat që do të shfaqen në flluska"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Flluskë"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Menaxho"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Flluska u hoq."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Trokit për të rifilluar këtë aplikacion për një pamje më të mirë."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Trokit për ta rinisur këtë aplikacion për një pamje më të mirë"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ndrysho raportin e pamjes së këtij aplikacioni te \"Cilësimet\""</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ndrysho raportin e pamjes"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ka probleme me kamerën?\nTrokit për ta ripërshtatur"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Rinis për një pamje më të mirë?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund të rinisësh aplikacionin në mënyrë që të duket më mirë në ekranin tënd, por mund të humbësh progresin ose çdo ndryshim të paruajtur"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund ta rinisësh aplikacionin në mënyrë që të duket më mirë në ekranin tënd, por mund ta humbasësh progresin ose çdo ndryshim të paruajtur"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 307efc9..cd532f7 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Апликација можда неће радити са подељеним екраном."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликација не подржава подељени екран."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ова апликација може да се отвори само у једном прозору"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Разделник подељеног екрана"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Премести горе десно"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Премести доле лево"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести доле десно"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"проширите облачић <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"скупите облачић <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Подешавања за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Одбаци облачић"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не користи облачиће за конверзацију"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Важи"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Нема недавних облачића"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Овде се приказују недавни и одбачени облачићи"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Ћаскајте у облачићима"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Нове конверзације се појављују као иконе у доњем углу екрана. Додирните да бисте их проширили или превуците да бисте их одбацили."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Контролишите облачиће у сваком тренутку"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Додирните овде и одредите које апликације и конверзације могу да имају облачић"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Облачић"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Додирните да бисте рестартовали ову апликацију ради бољег приказа."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Додирните да бисте рестартовали ову апликацију ради бољег приказа"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Промените размеру ове апликације у Подешавањима"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Промени размеру"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблема са камером?\nДодирните да бисте поново уклопили"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли да рестартујете ради бољег приказа?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, с тим што можете да изгубите оно што сте урадили или несачуване промене, ако их има"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, али можете да изгубите напредак или несачуване промене"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 33652cd..386dda7 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Appen kanske inte fungerar med delad skärm"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Appen har inte stöd för delad skärm"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Denna app kan bara vara öppen i ett fönster"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Avdelare för delad skärm"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Flytta högst upp till höger"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Flytta längst ned till vänster"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytta längst ned till höger"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"utöka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"komprimera <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Inställningar för <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Stäng bubbla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Visa inte konversationen i bubblor"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Inga nya bubblor"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"De senaste bubblorna och ignorerade bubblor visas här"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta med bubblor"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nya konversationer visas som ikoner nere i hörnet på skärmen. Tryck för att utöka eller dra för att stänga dem."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Styr bubblor när som helst"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tryck här för att hantera vilka appar och konversationer som får visas i bubblor"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Hantera"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubblan ignorerades."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Tryck för att starta om appen och få en bättre vy."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Tryck för att starta om appen och få en bättre vy"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ändra appens bildformat i inställningarna"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ändra bildformat"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problem med kameran?\nTryck för att anpassa på nytt"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index fe2ad1f..69b2e34 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Huenda programu isifanye kazi kwenye skrini iliyogawanywa"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Programu haifanyi kazi kwenye skrini iliyogawanywa"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Kitenganishi cha kugawa skrini"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Sogeza juu kulia"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Sogeza chini kushoto"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sogeza chini kulia"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"panua <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"kunja <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mipangilio ya <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ondoa kiputo"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Usiweke viputo kwenye mazungumzo"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Nimeelewa"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hakuna viputo vya hivi majuzi"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Viputo vya hivi karibuni na vile vilivyoondolewa vitaonekana hapa"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Piga gumzo ukitumia viputo"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Mazungumzo mapya huonekana kama aikoni katika kona ya chini ya skrini yako. Gusa ili uyapanue au buruta ili uyaondoe."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Dhibiti viputo wakati wowote"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Gusa hapa ili udhibiti programu na mazungumzo yanayoweza kutumia viputo"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Kiputo"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Dhibiti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Umeondoa kiputo."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Badilisha uwiano wa programu hii katika Mipangilio"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Badilisha uwiano wa kipengele"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Je, kuna hitilafu za kamera?\nGusa ili urekebishe"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index fd5f0e6..037b5ab 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"திரைப் பிரிப்புப் பயன்முறையில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"திரைப் பிரிப்புப் பயன்முறையை ஆப்ஸ் ஆதரிக்காது"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"திரைப் பிரிப்பான்"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"மேலே வலப்புறமாக நகர்த்து"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"கீழே இடப்புறமாக நகர்த்து"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"கீழே வலதுபுறமாக நகர்த்து"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> அமைப்புகள்"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"குமிழை அகற்று"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"உரையாடலைக் குமிழாக்காதே"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"சரி"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"சமீபத்திய குமிழ்கள் இல்லை"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"சமீபத்திய குமிழ்களும் நிராகரிக்கப்பட்ட குமிழ்களும் இங்கே தோன்றும்"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"குமிழ்களைப் பயன்படுத்தி உரையாடுங்கள்"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"புதிய உரையாடல்கள் உங்கள் திரையின் கீழ் மூலையில் ஐகான்களாகத் தோன்றும். அவற்றை விரிவாக்க தட்டவும் அல்லது நிராகரிக்க இழுக்கவும்."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"எப்போது வேண்டுமானாலும் குமிழ்களைக் கட்டுப்படுத்துங்கள்"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"எந்தெந்த ஆப்ஸும் உரையாடல்களும் குமிழியாகலாம் என்பதை நிர்வகிக்க இங்கே தட்டுங்கள்"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"அமைப்புகளில் இந்த ஆப்ஸின் தோற்ற விகிதத்தை மாற்றும்"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"தோற்ற விகிதத்தை மாற்றும்"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 6f95aa9..694ecb9 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"స్ప్లిట్ స్క్రీన్తో యాప్ పని చేయకపోవచ్చు"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"యాప్లో స్ప్లిట్ స్క్రీన్కు సపోర్ట్ లేదు"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"ఈ యాప్ను 1 విండోలో మాత్రమే తెరవవచ్చు"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ఎగువ కుడివైపునకు జరుపు"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"దిగువ ఎడమవైపునకు తరలించు"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"దిగవు కుడివైపునకు జరుపు"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> విస్తరించండి"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ను కుదించండి"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> సెట్టింగ్లు"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"బబుల్ను విస్మరించు"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"సంభాషణను బబుల్ చేయవద్దు"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"అర్థమైంది"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ఇటీవలి బబుల్స్ ఏవీ లేవు"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ఇటీవలి బబుల్స్ మరియు తీసివేసిన బబుల్స్ ఇక్కడ కనిపిస్తాయి"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"బబుల్స్ను ఉపయోగించి చాట్ చేయండి"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"కొత్త సంభాషణలు మీ స్క్రీన్ కింద మూలన చిహ్నాలుగా కనిపిస్తాయి. ట్యాప్ చేసి వాటిని విస్తరించండి లేదా లాగి విస్మరించండి."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"బబుల్స్ను ఎప్పుడైనా కంట్రోల్ చేయండి"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ఏ యాప్లు, సంభాషణలను బబుల్ చేయాలో మేనేజ్ చేయడానికి ఇక్కడ ట్యాప్ చేయండి"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"బబుల్"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"మెరుగైన వీక్షణ కోసం ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"మెరుగైన వీక్షణ కోసం ఈ యాప్ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"సెట్టింగ్లలో ఈ యాప్ ఆకార నిష్పత్తిని మార్చండి"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ఆకార నిష్పత్తిని మార్చండి"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"కెమెరా సమస్యలు ఉన్నాయా?\nరీఫిట్ చేయడానికి ట్యాప్ చేయండి"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string>
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index cc0333e..464e132 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -45,11 +45,21 @@
<integer name="config_pipForceCloseDelay">5000</integer>
<!-- Animation duration when exit starting window: fade out icon -->
- <integer name="starting_window_app_reveal_icon_fade_out_duration">0</integer>
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer>
+
+ <!-- Animation delay when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">200</integer>
<!-- Animation duration when exit starting window: reveal app -->
- <integer name="starting_window_app_reveal_anim_delay">0</integer>
+ <integer name="starting_window_app_reveal_anim_duration">300</integer>
- <!-- Animation duration when exit starting window: reveal app -->
- <integer name="starting_window_app_reveal_anim_duration">0</integer>
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">1</integer>
+
+ <!-- Whether this device allows to use the appIcon as a fallback icon for the splash screen
+ window. If false, the splash screen will be a solid color splash screen whenever the
+ app has not provided a windowSplashScreenAnimatedIcon. -->
+ <bool name="config_canUseAppIconForSplashScreen">false</bool>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 6733940..d4b6aff 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"แอปอาจใช้ไม่ได้กับโหมดแยกหน้าจอ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"แอปไม่รองรับการแยกหน้าจอ"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"เส้นแยกหน้าจอ"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ย้ายไปด้านขวาบน"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ย้ายไปด้านซ้ายล่าง"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ย้ายไปด้านขาวล่าง"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ขยาย <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ยุบ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"การตั้งค่า <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ปิดบับเบิล"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ไม่ต้องแสดงการสนทนาเป็นบับเบิล"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"รับทราบ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ไม่มีบับเบิลเมื่อเร็วๆ นี้"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"บับเบิลที่แสดงและที่ปิดไปเมื่อเร็วๆ นี้จะปรากฏที่นี่"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"แชทโดยใช้บับเบิล"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"การสนทนาครั้งใหม่ๆ จะปรากฏเป็นไอคอนที่มุมล่างของหน้าจอ โดยสามารถแตะเพื่อขยายหรือลากเพื่อปิด"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ควบคุมบับเบิลได้ทุกเมื่อ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"แตะที่นี่เพื่อจัดการแอปและการสนทนาที่แสดงเป็นบับเบิลได้"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"บับเบิล"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"เปลี่ยนสัดส่วนภาพของแอปนี้ในการตั้งค่า"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"เปลี่ยนอัตราส่วนกว้างยาว"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"หากพบปัญหากับกล้อง\nแตะเพื่อแก้ไข"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 8cf4eb484..db9303c 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Posibleng hindi gumana sa split screen ang app"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Hindi sinusuportahan ng app ang split-screen"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Sa 1 window lang puwedeng buksan ang app na ito"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Divider ng split screen"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Ilipat sa kanan sa itaas"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Ilipat sa kaliwa sa ibaba"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ilipat sa kanan sa ibaba"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"I-expand ang <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"i-collapse ang <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mga setting ng <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"I-dismiss ang bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Huwag ipakita sa bubble ang mga pag-uusap"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Walang kamakailang bubble"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Lalabas dito ang mga kamakailang bubble at na-dismiss na bubble"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Mag-chat gamit ang mga bubble"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Lalabas ang mga bagong pag-uusap bilang mga icon sa sulok sa ibaba ng iyong screen. I-tap para i-expand ang mga ito o i-drag para i-dismiss ang mga ito."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolin ang mga bubble anumang oras"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Mag-tap dito para pamahalaan ang mga app at conversion na puwedeng mag-bubble"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pamahalaan"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Na-dismiss na ang bubble."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"I-tap para i-restart ang app na ito para sa mas magandang view."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"I-tap para i-restart ang app na ito para sa mas magandang view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Baguhin ang aspect ratio ng app na ito sa Mga Setting"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Baguhin ang aspect ratio"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"May mga isyu sa camera?\nI-tap para i-refit"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 1454435..818666c 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Uygulama bölünmüş ekranda çalışmayabilir"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Uygulama bölünmüş ekranı desteklemiyor."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Bu uygulama yalnızca 1 pencerede açılabilir"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcı"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Sağ üste taşı"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Sol alta taşı"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sağ alta taşı"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"genişlet: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"daralt: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baloncuğu kapat"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Görüşmeyi baloncuk olarak görüntüleme"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Anladım"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Son kapatılan baloncuk yok"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Son baloncuklar ve kapattığınız baloncuklar burada görünür"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Baloncukları kullanarak sohbet edin"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Yeni görüşmeler, ekranınızın alt köşesinde simge olarak görünür. Bunları dokunarak genişletebilir veya sürükleyerek kapatabilirsiniz."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Baloncukları istediğiniz zaman kontrol edin"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Buraya dokunarak baloncuk olarak gösterilecek uygulama ve görüşmeleri yönetin"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Baloncuk"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Yönet"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon kapatıldı."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Bu uygulamanın en boy oranını Ayarlar\'dan değiştirin"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"En boy oranını değiştir"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kameranızda sorun mu var?\nDüzeltmek için dokunun"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 78df129..85fb8e1 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Додаток може не працювати в режимі розділення екрана"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Додаток не підтримує розділення екрана"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Цей додаток можна відкрити лише в одному вікні"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Розділювач екрана"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Перемістити праворуч угору"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Перемістити ліворуч униз"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перемістити праворуч униз"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"розгорнути \"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>\""</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"згорнути \"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>\""</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Налаштування параметра \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Закрити підказку"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не показувати спливаючі чати для розмов"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Зрозуміло"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Немає нещодавніх спливаючих чатів"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Тут з\'являтимуться нещодавні й закриті спливаючі чати"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Спливаючий чат"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Нові розмови відображаються у вигляді значків у нижньому куті екрана. Торкніться, щоб розгорнути їх, або перетягніть, щоб закрити."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Контроль спливаючих чатів"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Натисніть тут, щоб вибрати, для яких додатків і розмов дозволити спливаючі чати"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Спливаюче сповіщення"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змінити формат для цього додатка в налаштуваннях"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змінити формат"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми з камерою?\nНатисніть, щоб пристосувати"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index ca16424..813870b 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ایپ اسپلٹ اسکرین کو سپورٹ نہیں کرتی ہے"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"اسپلٹ اسکرین ڈیوائیڈر"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"اوپر دائیں جانب لے جائيں"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"نیچے بائیں جانب لے جائیں"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نیچے دائیں جانب لے جائیں"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> کو پھیلائیں"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> کو سکیڑیں"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ترتیبات"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"بلبلہ برخاست کریں"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"گفتگو بلبلہ نہ کریں"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"سمجھ آ گئی"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"کوئی حالیہ بلبلہ نہیں"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حالیہ بلبلے اور برخاست شدہ بلبلے یہاں ظاہر ہوں گے"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"بلبلے کے ذریعے چیٹ کریں"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"نئی گفتگوئیں آپ کی اسکرین کے نیچے کونے میں آئیکنز کے طور پر ظاہر ہوتی ہیں۔ انہیں پھیلانے کے لیے تھپتھپائیں یا انہیں برخاست کرنے کے لیے گھسیٹیں۔"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کسی بھی وقت بلبلے کو کنٹرول کریں"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"یہ نظم کرنے کے لیے یہاں تھپتھپائیں کہ کون سی ایپس اور گفتگوئیں بلبلہ سکتی ہیں"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"بلبلہ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں۔"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ترتیبات میں اس ایپ کی تناسبی شرح کو تبدیل کریں"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تناسبی شرح کو تبدیل کریں"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"کیمرے کے مسائل؟\nدوبارہ فٹ کرنے کیلئے تھپتھپائیں"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index c0dc033..7bcacbb 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Bu ilovada ekranni ikkiga ajratish rejimi ishlamaydi."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Bu ilovada ekranni ikkiga ajratish ishlamaydi."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Bu ilovani faqat 1 ta oynada ochish mumkin"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Ekranni ikkiga ajratish chizigʻi"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Yuqori oʻngga surish"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Quyi chapga surish"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Quyi oʻngga surish"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ni yoyish"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ni yopish"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> sozlamalari"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bulutchani yopish"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Suhbatlar bulutchalar shaklida chiqmasin"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hech qanday bulutcha topilmadi"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Eng oxirgi va yopilgan bulutchali chatlar shu yerda chiqadi"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bulutchalar yordamida suhbatlashish"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Yangi suhbatlar ekraningizning pastki burchagida belgilar shaklida koʻrinadi. Ochish uchun bosing yoki yopish uchun torting"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bulutchalardagi bildirishnomalar"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Bulutchalarda bildirishnomalar chiqishiga ruxsat beruvchi ilova va suhbatlarni tanlang."</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Pufaklar"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Boshqarish"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulutcha yopildi."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Sozlamalar orqali bu ilovaning tomonlar nisbatini oʻzgartiring"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tomonlar nisbatini oʻzgartirish"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera nosozmi?\nQayta moslash uchun bosing"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 7d97400..416dd91 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Có thể ứng dụng không dùng được chế độ chia đôi màn hình"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Ứng dụng không hỗ trợ chế độ chia đôi màn hình"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Ứng dụng này chỉ có thể mở trong 1 cửa sổ"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Trình chia đôi màn hình"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Chuyển lên trên cùng bên phải"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Chuyển tới dưới cùng bên trái"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Chuyển tới dưới cùng bên phải"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"mở rộng <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"thu gọn <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cài đặt <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Đóng bong bóng"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Dừng sử dụng bong bóng cho cuộc trò chuyện"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Đã hiểu"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Không có bong bóng trò chuyện nào gần đây"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Bong bóng trò chuyện đã đóng và bong bóng trò chuyện gần đây sẽ xuất hiện ở đây"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Trò chuyện bằng bong bóng trò chuyện"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Các cuộc trò chuyện mới sẽ xuất hiện dưới dạng biểu tượng ở góc dưới màn hình. Hãy nhấn vào các cuộc trò chuyện đó để mở rộng hoặc kéo để bỏ qua."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kiểm soát bong bóng bất cứ lúc nào"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Nhấn vào đây để quản lý việc dùng bong bóng cho các ứng dụng và cuộc trò chuyện"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bong bóng"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Nhấn để khởi động lại ứng dụng này để xem tốt hơn."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Nhấn nút khởi động lại ứng dụng này để xem dễ hơn"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Thay đổi tỷ lệ khung hình của ứng dụng này thông qua phần Cài đặt"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Thay đổi tỷ lệ khung hình"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Có vấn đề với máy ảnh?\nHãy nhấn để sửa lỗi"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string>
diff --git a/libs/WindowManager/Shell/res/values-watch/colors.xml b/libs/WindowManager/Shell/res/values-watch/colors.xml
new file mode 100644
index 0000000..82492bf
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2020, 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.
+ */
+-->
+<resources>
+ <color name="splash_window_background_default">@color/splash_screen_bg_dark</color>
+</resources>
+
diff --git a/libs/WindowManager/Shell/res/values-watch/config.xml b/libs/WindowManager/Shell/res/values-watch/config.xml
new file mode 100644
index 0000000..03736ed
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/config.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for watch products. Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Animation duration when exit starting window: fade out icon -->
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">50</integer>
+
+ <!-- Animation delay when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">50</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_duration">200</integer>
+
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">1</integer>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-watch/dimen.xml b/libs/WindowManager/Shell/res/values-watch/dimen.xml
new file mode 100644
index 0000000..362e72c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/dimen.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+-->
+<resources>
+ <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (48 X 48) / (72 x 72) -->
+ <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+ <!-- Scaling factor applied to splash icons without provided background i.e. (60 / 48) -->
+ <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.25</item>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index d1f50db..6ad1728 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"应用可能无法在分屏模式下正常运行"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"应用不支持分屏"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"此应用只能在 1 个窗口中打开"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"分屏分隔线"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"移至右上角"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"移至左下角"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下角"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"展开“<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>”"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"收起“<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>”"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭对话泡"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不以对话泡形式显示对话"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"知道了"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近没有对话泡"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"此处会显示最近的对话泡和已关闭的对话泡"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"使用对话泡聊天"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"新对话会以图标形式显示在屏幕底部的角落中。点按图标即可展开对话,拖动图标即可关闭对话。"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"随时控制对话泡"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"点按此处即可管理哪些应用和对话可以显示对话泡"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭对话泡。"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"点按即可重启此应用,获得更好的视图体验。"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"点按即可重启此应用,获得更好的视觉体验"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"在“设置”中更改此应用的宽高比"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"更改高宽比"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相机有问题?\n点按即可整修"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 6f399e5..b5b94ec 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"畫中畫選單"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在畫中畫模式"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"如果您不想「<xliff:g id="NAME">%s</xliff:g>」使用此功能,請輕按以開啟設定,然後停用此功能。"</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"如果你不想「<xliff:g id="NAME">%s</xliff:g>」使用此功能,請輕按以開啟設定,然後停用此功能。"</string>
<string name="pip_play" msgid="3496151081459417097">"播放"</string>
<string name="pip_pause" msgid="690688849510295232">"暫停"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"跳到下一個"</string>
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割螢幕中運作"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"應用程式不支援分割螢幕"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"此應用程式只可在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"分割螢幕分隔線"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"移去右上角"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"移去左下角"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移去右下角"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"打開<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"收埋<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"關閉小視窗氣泡"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不要透過小視窗顯示對話"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"知道了"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"沒有最近曾使用的小視窗"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近使用和關閉的小視窗會在這裡顯示"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"使用對話氣泡進行即時通訊"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"畫面底部的角落會顯示新對話圖示。輕按即可展開圖示;拖曳即可關閉。"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"隨時控制對話氣泡"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"輕按這裡即可管理哪些應用程式和對話可以使用對話氣泡"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"氣泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗。"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"前往「設定」變更此應用程式的長寬比"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"變更長寬比"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題?\n輕按即可修正"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string>
@@ -88,8 +96,8 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string>
- <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及您作出的任何變更"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動以改善檢視畫面嗎?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及你作出的任何變更"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 4ca49e1..0f2a052 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割畫面中運作"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"這個應用程式不支援分割畫面"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"這個應用程式只能在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
<string name="accessibility_divider" msgid="6407584574218956849">"分割畫面分隔線"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"移至右上方"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"移至左下方"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下方"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"展開「<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>」"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"收合「<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>」"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"關閉對話框"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不要以對話框形式顯示對話"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"我知道了"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近沒有任何對話框"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近的對話框和已關閉的對話框會顯示在這裡"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"透過對話框進行即時通訊"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"畫面底部的角落會顯示新對話圖示。輕觸可展開圖示,拖曳即可關閉。"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"你隨時可以控管對話框的各項設定"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"輕觸這裡即可管理哪些應用程式和對話可顯示對話框"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"泡泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"請輕觸並重新啟動此應用程式,取得更良好的觀看體驗。"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"輕觸此按鈕重新啟動這個應用程式,即可獲得更良好的觀看體驗"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"前往「設定」變更這個應用程式的顯示比例"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"變更顯示比例"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題嗎?\n輕觸即可修正"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 478b5a6..a696f9e 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -34,7 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
<string name="dock_forced_resizable" msgid="7429086980048964687">"Ama-app okungenzeka angasebenzi ngesikrini esihlukanisiwe"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"I-app ayisekeli isikrini esihlukanisiwe."</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Le-app ingavulwa kuphela ewindini eli-1."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Isihlukanisi sokuhlukanisa isikrini"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Hambisa phezulu ngakwesokudla"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Hambisa inkinobho ngakwesokunxele"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Hambisa inkinobho ngakwesokudla"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"nweba <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"goqa <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> izilungiselelo"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cashisa ibhamuza"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ungayibhamuzi ingxoxo"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ngiyezwa"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Awekho amabhamuza akamuva"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Amabhamuza akamuva namabhamuza asusiwe azobonakala lapha."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Xoxa usebenzisa amabhamuza"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Izingxoxo ezintsha zivela njengezithonjana ekhoneni eliphansi lesikrini sakho. Thepha ukuze uzikhulise noma uhudule ukuze uzichithe."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Lawula amabhamuza noma nini"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Thepha lapha ukuze ulawule ukuthi yimaphi ama-app kanye nezingxoxo ezingenza amabhamuza"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Ibhamuza"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Phatha"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ibhamuza licashisiwe."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Shintsha ukubukeka kwesilinganiselo kwe-app kuMasethingi"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Shintsha ukubukeka kwesilinganiselo"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Izinkinga zekhamera?\nThepha ukuze uyilinganise kabusha"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index b2ec98b..fae71ef 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -60,17 +60,10 @@
<!-- Desktop Mode -->
<color name="desktop_mode_caption_handle_bar_light">#EFF1F2</color>
<color name="desktop_mode_caption_handle_bar_dark">#1C1C17</color>
- <color name="desktop_mode_caption_expand_button_light">#EFF1F2</color>
- <color name="desktop_mode_caption_expand_button_dark">#48473A</color>
- <color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
- <color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
- <color name="desktop_mode_caption_maximize_button_light">#EFF1F2</color>
- <color name="desktop_mode_caption_maximize_button_dark">#1C1C17</color>
- <color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
- <color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
- <color name="desktop_mode_caption_menu_text_color">#191C1D</color>
- <color name="desktop_mode_caption_menu_buttons_color_inactive">#191C1D</color>
- <color name="desktop_mode_caption_menu_buttons_color_active">#00677E</color>
<color name="desktop_mode_resize_veil_light">#EFF1F2</color>
<color name="desktop_mode_resize_veil_dark">#1C1C17</color>
+ <color name="desktop_mode_maximize_menu_button">#DDDACD</color>
+ <color name="desktop_mode_maximize_menu_button_outline">#797869</color>
+ <color name="desktop_mode_maximize_menu_button_outline_on_hover">#606219</color>
+ <color name="desktop_mode_maximize_menu_button_on_hover">#E7E790</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a3916b7..e4abae4 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -58,6 +58,9 @@
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">1000</integer>
+ <!-- Allow PIP to resize via pinch gesture. -->
+ <bool name="config_pipEnablePinchResize">true</bool>
+
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">250</integer>
@@ -83,6 +86,11 @@
<!-- Animation duration when exit starting window: reveal app -->
<integer name="starting_window_app_reveal_anim_duration">266</integer>
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">0</integer>
+
<!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
These values are in DPs and will be converted to pixel sizes internally. -->
<string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
@@ -128,4 +136,12 @@
<!-- Whether DragAndDrop capability is enabled -->
<bool name="config_enableShellDragDrop">true</bool>
+
+ <!-- Whether this device allows to use the appIcon as a fallback icon for the splash screen
+ window. If false, the splash screen will be a solid color splash screen whenever the
+ app has not provided a windowSplashScreenAnimatedIcon. -->
+ <bool name="config_canUseAppIconForSplashScreen">true</bool>
+
+ <!-- Whether CompatUIController is enabled -->
+ <bool name="config_enableCompatUIController">true</bool>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/config_tv.xml b/libs/WindowManager/Shell/res/values/config_tv.xml
new file mode 100644
index 0000000..3da5539
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/config_tv.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+<resources>
+ <integer name="config_tvPipEnterFadeOutDuration">500</integer>
+ <integer name="config_tvPipEnterFadeInDuration">1500</integer>
+ <integer name="config_tvPipExitFadeOutDuration">500</integer>
+ <integer name="config_tvPipExitFadeInDuration">500</integer>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 99526de..8f9de61 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -96,6 +96,9 @@
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
<!-- Divider handle size for split screen -->
+ <dimen name="split_divider_handle_region_width">96dp</dimen>
+ <dimen name="split_divider_handle_region_height">48dp</dimen>
+
<dimen name="split_divider_handle_width">72dp</dimen>
<dimen name="split_divider_handle_height">3dp</dimen>
@@ -143,7 +146,9 @@
<dimen name="bubble_expanded_view_padding">16dp</dimen>
<!-- Padding for the edge of the expanded view that is closest to the edge of the screen used
when displaying in landscape on a large screen. -->
- <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen>
+ <dimen name="bubble_expanded_view_largescreen_landscape_padding">102dp</dimen>
+ <!-- The width of the expanded view on large screens. -->
+ <dimen name="bubble_expanded_view_largescreen_width">540dp</dimen>
<!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
a slight touch slop around the expanded view. -->
<dimen name="bubble_expanded_view_slop">8dp</dimen>
@@ -183,10 +188,11 @@
<dimen name="bubble_pointer_overlap">1dp</dimen>
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
- <!-- Height of button allowing users to adjust settings for bubbles. -->
- <dimen name="bubble_manage_button_height">36dp</dimen>
- <!-- Height of manage button including margins. -->
- <dimen name="bubble_manage_button_total_height">68dp</dimen>
+ <!-- Height of button allowing users to adjust settings for bubbles. We use sp so that the
+ button can scale with the font size. -->
+ <dimen name="bubble_manage_button_height">36sp</dimen>
+ <!-- Touch area height of the manage button. -->
+ <dimen name="bubble_manage_button_touch_area_height">48dp</dimen>
<!-- The margin around the outside of the manage button. -->
<dimen name="bubble_manage_button_margin">16dp</dimen>
<!-- Height of an item in the bubble manage menu. -->
@@ -221,9 +227,9 @@
<dimen name="bubbles_user_education_width">480dp</dimen>
<!-- Margin applied to the end of the user education views (really only matters for phone
since the width is match parent). -->
- <dimen name="bubble_user_education_margin_end">24dp</dimen>
+ <dimen name="bubble_user_education_margin_horizontal">24dp</dimen>
<!-- Padding applied to the end of the user education view. -->
- <dimen name="bubble_user_education_padding_end">58dp</dimen>
+ <dimen name="bubble_user_education_padding_horizontal">58dp</dimen>
<!-- Padding between the bubble and the user education text. -->
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
<!-- Max width for the bubble popup view. -->
@@ -401,6 +407,30 @@
<!-- Height of button (32dp) + 2 * margin (5dp each). -->
<dimen name="freeform_decor_caption_height">42dp</dimen>
+ <!-- Height of desktop mode caption for freeform tasks. -->
+ <dimen name="desktop_mode_freeform_decor_caption_height">42dp</dimen>
+
+ <!-- Height of desktop mode caption for fullscreen tasks. -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+
+ <!-- The width of the maximize menu in desktop mode. -->
+ <dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
+
+ <!-- The height of the maximize menu in desktop mode. -->
+ <dimen name="desktop_mode_maximize_menu_height">112dp</dimen>
+
+ <!-- The larger of the two corner radii of the maximize menu buttons. -->
+ <dimen name="desktop_mode_maximize_menu_buttons_large_corner_radius">4dp</dimen>
+
+ <!-- The smaller of the two corner radii of the maximize menu buttons. -->
+ <dimen name="desktop_mode_maximize_menu_buttons_small_corner_radius">2dp</dimen>
+
+ <!-- The corner radius of the maximize menu. -->
+ <dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen>
+
+ <!-- The radius of the Maximize menu shadow. -->
+ <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
+
<!-- The width of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
@@ -411,7 +441,10 @@
<dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
<!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
+
+ <!-- The height of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_height">328dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
@@ -425,6 +458,9 @@
<!-- The radius of the caption menu corners. -->
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
+ <!-- The radius of the caption menu icon. -->
+ <dimen name="desktop_mode_caption_icon_radius">28dp</dimen>
+
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
@@ -439,4 +475,16 @@
<!-- The height of the area at the top of the screen where a freeform task will transition to
fullscreen if dragged until the top bound of the task is within the area. -->
<dimen name="desktop_mode_transition_area_height">16dp</dimen>
+
+ <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
+ <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+ <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
+ <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.2</item>
+
+ <!-- The margin between the entering window and the exiting window during cross task back -->
+ <dimen name="cross_task_back_inter_window_margin">14dp</dimen>
+ <!-- The vertical margin that needs to be preserved between the scaled window bounds and the
+ original window bounds (once the surface is scaled enough to do so) -->
+ <dimen name="cross_task_back_vertical_margin">8dp</dimen>
+
</resources>
diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml
index 8831b61..bc59a23 100644
--- a/libs/WindowManager/Shell/res/values/ids.xml
+++ b/libs/WindowManager/Shell/res/values/ids.xml
@@ -42,4 +42,6 @@
<item type="id" name="action_move_top_right"/>
<item type="id" name="action_move_bottom_left"/>
<item type="id" name="action_move_bottom_right"/>
+
+ <item type="id" name="dismiss_view"/>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 00c63d7..3e66bbb 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -72,7 +72,7 @@
<!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead [CHAR LIMIT=NONE] -->
<string name="dock_non_resizeble_failed_to_dock_text">App does not support split screen</string>
<!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
- <string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window.</string>
+ <string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window</string>
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="forced_resizable_secondary_display">App may not work on a secondary display.</string>
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
@@ -142,6 +142,10 @@
<string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
<!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
<string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
+ <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]-->
+ <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string>
+ <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]-->
+ <string name="bubble_accessibility_announce_collapse">collapse <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string>
<!-- Label for the button that takes the user to the notification settings for the given app. -->
<string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
<!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
@@ -163,6 +167,10 @@
<!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
<string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
+ <!-- Title text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
+ <string name="bubble_bar_education_stack_title">Chat using bubbles</string>
+ <!-- Descriptive text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
+ <string name="bubble_bar_education_stack_text">New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.</string>
<!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
<string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
<!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index d902fd4..08c2a02 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- Theme used for the activity that shows when the system forced an app to be resizable -->
<style name="ForcedResizableTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
<item name="android:windowBackground">@drawable/forced_resizable_background</item>
@@ -37,7 +38,7 @@
<item name="android:padding">16dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textFontWeight">500</item>
- <item name="android:textColor">@color/desktop_mode_caption_menu_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:drawablePadding">16dp</item>
<item name="android:background">?android:selectableItemBackground</item>
</style>
@@ -59,20 +60,9 @@
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">@dimen/split_divider_bar_width</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:background">@color/split_divider_background</item>
- </style>
-
- <style name="DockedDividerMinimizedShadow">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">8dp</item>
- </style>
-
- <style name="DockedDividerHandle">
+ <item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center</item>
- <item name="android:layout_width">96dp</item>
- <item name="android:layout_height">48dp</item>
+ <item name="android:background">@color/split_divider_background</item>
</style>
<style name="TvPipEduText">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 34bf9e0..2e5448a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import android.annotation.SuppressLint;
import android.app.WindowConfiguration;
import android.util.SparseArray;
import android.view.SurfaceControl;
@@ -29,6 +30,7 @@
import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
import java.util.List;
@@ -44,9 +46,14 @@
/** Display area leashes, which is mapped by display IDs. */
private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
- public RootDisplayAreaOrganizer(Executor executor) {
+ public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) {
super(executor);
- List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @SuppressLint("MissingPermission") // Only called by SysUI.
+ private void onInit() {
+ final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
for (int i = infos.size() - 1; i >= 0; --i) {
onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 410ae78d..5143d41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+import android.annotation.SuppressLint;
import android.annotation.UiContext;
import android.app.ResourcesManager;
import android.content.Context;
@@ -32,11 +33,13 @@
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
+import android.window.SystemPerformanceHinter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -58,12 +61,27 @@
/** {@link DisplayAreaContext} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
+ private final SystemPerformanceHinter.DisplayRootProvider mPerfRootProvider =
+ new SystemPerformanceHinter.DisplayRootProvider() {
+ @Override
+ public SurfaceControl getRootForDisplay(int displayId) {
+ return mLeashes.get(displayId);
+ }
+ };
+
private final Context mContext;
- public RootTaskDisplayAreaOrganizer(Executor executor, Context context) {
+ public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context,
+ @NonNull ShellInit shellInit) {
super(executor);
mContext = context;
- List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @SuppressLint("MissingPermission") // Only called by SysUI.
+ private void onInit() {
+ final List<DisplayAreaAppearedInfo> infos =
+ registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
for (int i = infos.size() - 1; i >= 0; --i) {
onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
}
@@ -97,6 +115,19 @@
b.setParent(sc);
}
+ /**
+ * Re-parents the provided surface to the leash of the provided display.
+ *
+ * @param displayId the display area to reparent to.
+ * @param sc the surface to be reparented.
+ * @param t a {@link SurfaceControl.Transaction} in which to reparent.
+ */
+ public void reparentToDisplayArea(int displayId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ final SurfaceControl displayAreaLeash = mLeashes.get(displayId);
+ t.reparent(sc, displayAreaLeash);
+ }
+
public void setPosition(@NonNull SurfaceControl.Transaction tx, int displayId, int x, int y) {
final SurfaceControl sc = mLeashes.get(displayId);
if (sc == null) {
@@ -229,6 +260,11 @@
return mDisplayAreaContexts.get(displayId);
}
+ @NonNull
+ public SystemPerformanceHinter.DisplayRootProvider getPerformanceRootProvider() {
+ return mPerfRootProvider;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 0479576..fe65fdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,6 +16,7 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -29,6 +30,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.LocusId;
@@ -167,6 +169,13 @@
private final Object mLock = new Object();
private StartingWindowController mStartingWindow;
+ /** Overlay surface for home root task */
+ private final SurfaceControl mHomeTaskOverlayContainer = new SurfaceControl.Builder()
+ .setName("home_task_overlay_container")
+ .setContainerLayer()
+ .setHidden(false)
+ .build();
+
/**
* In charge of showing compat UI. Can be {@code null} if the device doesn't support size
* compat or if this isn't the main {@link ShellTaskOrganizer}.
@@ -427,6 +436,14 @@
}
}
+ /**
+ * Returns a surface which can be used to attach overlays to the home root task
+ */
+ @NonNull
+ public SurfaceControl getHomeTaskOverlayContainer() {
+ return mHomeTaskOverlayContainer;
+ }
+
@Override
public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
@@ -484,6 +501,15 @@
if (mUnfoldAnimationController != null) {
mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
+
+ if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+ ProtoLog.v(WM_SHELL_TASK_ORG, "Adding overlay to home task");
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setLayer(mHomeTaskOverlayContainer, Integer.MAX_VALUE);
+ t.reparent(mHomeTaskOverlayContainer, info.getLeash());
+ t.apply();
+ }
+
notifyLocusVisibilityIfNeeded(info.getTaskInfo());
notifyCompatUI(info.getTaskInfo(), listener);
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
@@ -578,6 +604,12 @@
notifyCompatUI(taskInfo, null /* taskListener */);
// Notify the recent tasks that a task has been removed
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo));
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.reparent(mHomeTaskOverlayContainer, null);
+ t.apply();
+ ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface");
+ }
if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) {
// Preemptively clean up the leash only if shell transitions are not enabled
@@ -700,7 +732,7 @@
@Override
public void onCameraControlStateUpdated(
- int taskId, @TaskInfo.CameraCompatControlState int state) {
+ int taskId, @AppCompatTaskInfo.CameraCompatControlState int state) {
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -754,7 +786,7 @@
// The task is vanished or doesn't support compat UI, notify to remove compat UI
// on this Task if there is any.
if (taskListener == null || !taskListener.supportCompatUI()
- || !taskInfo.hasCompatUI() || !taskInfo.isVisible) {
+ || !taskInfo.appCompatTaskInfo.hasCompatUI() || !taskInfo.isVisible) {
mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 5cf9175..8241e1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -136,6 +136,7 @@
/** Called on frame update. */
final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ mTransformation.clear();
// Extract the transformation to the current time.
mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
mTransformation);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 4d87c95..ac75c73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -255,8 +255,13 @@
int offsetLayer = TYPE_LAYER_OFFSET;
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
for (TransitionInfo.Change change : openingChanges) {
+ final Animation animation =
+ animationProvider.get(info, change, openingWholeScreenBounds);
+ if (animation.getDuration() == 0) {
+ continue;
+ }
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- info, change, animationProvider, openingWholeScreenBounds);
+ info, change, animation, openingWholeScreenBounds);
if (isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -275,8 +280,13 @@
adapters.add(snapshotAdapter);
}
}
+ final Animation animation =
+ animationProvider.get(info, change, closingWholeScreenBounds);
+ if (animation.getDuration() == 0) {
+ continue;
+ }
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- info, change, animationProvider, closingWholeScreenBounds);
+ info, change, animation, closingWholeScreenBounds);
if (!isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -353,8 +363,7 @@
@NonNull
private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
- @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
- final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
+ @NonNull Animation animation, @NonNull Rect wholeAnimationBounds) {
return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
wholeAnimationBounds, TransitionUtil.getRootFor(change, info));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 4640106..efa5a1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -17,9 +17,14 @@
package com.android.wm.shell.activityembedding;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.view.animation.AlphaAnimation;
@@ -32,8 +37,6 @@
import android.view.animation.TranslateAnimation;
import android.window.TransitionInfo;
-import androidx.annotation.NonNull;
-
import com.android.internal.policy.TransitionAnimation;
import com.android.wm.shell.util.TransitionUtil;
@@ -199,8 +202,11 @@
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
+ final Animation customAnimation = loadCustomAnimation(info, isEnter);
final Animation animation;
- if (shouldShowBackdrop(info, change)) {
+ if (customAnimation != null) {
+ animation = customAnimation;
+ } else if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_open_enter
: com.android.internal.R.anim.task_fragment_clear_top_open_exit);
@@ -223,8 +229,11 @@
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
+ final Animation customAnimation = loadCustomAnimation(info, isEnter);
final Animation animation;
- if (shouldShowBackdrop(info, change)) {
+ if (customAnimation != null) {
+ animation = customAnimation;
+ } else if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_close_enter
: com.android.internal.R.anim.task_fragment_clear_top_close_exit);
@@ -245,8 +254,26 @@
private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
- final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
+ final int type = getTransitionTypeFromInfo(info);
+ final Animation a = loadAttributeAnimation(type, info, change, WALLPAPER_TRANSITION_NONE,
mTransitionAnimation, false);
return a != null && a.getShowBackdrop();
}
+
+ @Nullable
+ private Animation loadCustomAnimation(@NonNull TransitionInfo info, boolean isEnter) {
+ final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+ if (options == null || options.getType() != ANIM_CUSTOM) {
+ return null;
+ }
+ final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
+ isEnter ? options.getEnterResId() : options.getExitResId());
+ if (anim != null) {
+ return anim;
+ }
+ // The app may be intentional to use an invalid resource as a no-op animation.
+ // ActivityEmbeddingAnimationRunner#createOpenCloseAnimationAdapters will skip the
+ // animation with duration 0. Then it will use prepareForJumpCut for empty adapters.
+ return new AlphaAnimation(1f, 1f);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 06ce371..b4e852c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -87,42 +88,54 @@
mTransitions.addHandler(this);
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean containsEmbeddingSplit = false;
- boolean containsNonEmbeddedChange = false;
- final List<TransitionInfo.Change> changes = info.getChanges();
- for (int i = changes.size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = changes.get(i);
- if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- containsNonEmbeddedChange = true;
- } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
+ /** Whether ActivityEmbeddingController should animate this transition. */
+ public boolean shouldAnimate(@NonNull TransitionInfo info) {
+ boolean containsEmbeddingChange = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
+ FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
- containsEmbeddingSplit = true;
+ containsEmbeddingChange = true;
}
}
- if (!containsEmbeddingSplit) {
+ if (!containsEmbeddingChange) {
// Let the system to play the default animation if there is no ActivityEmbedding split
// window. This allows to play the app customized animation when there is no embedding,
// such as the device is in a folded state.
return false;
}
- if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+
+ if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
return false;
}
+
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
- if (options != null
- // Scene-transition will be handled by app side.
- && (options.getType() == ANIM_SCENE_TRANSITION
- // Use default transition handler to animate override animation.
- || isSupportedOverrideAnimation(options))) {
- return false;
+ if (options != null) {
+ // Scene-transition should be handled by app side.
+ if (options.getType() == ANIM_SCENE_TRANSITION) {
+ return false;
+ }
+ // The case of ActivityOptions#makeCustomAnimation, Activity#overridePendingTransition,
+ // and Activity#overrideActivityTransition are supported.
+ if (options.getType() == ANIM_CUSTOM) {
+ return true;
+ }
+ // Use default transition handler to animate other override animation.
+ return !isSupportedOverrideAnimation(options);
}
+ return true;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (!shouldAnimate(info)) return false;
+
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +149,16 @@
mAnimationRunner.cancelAnimationFromMerge();
}
+ /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
+ private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index c980906..8d8dc10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -49,6 +49,11 @@
@BackEvent.SwipeEdge int swipeEdge);
/**
+ * Called when the input pointers are pilfered.
+ */
+ void onPilferPointers();
+
+ /**
* Sets whether the back gesture is past the trigger threshold or not.
*/
void setTriggerBack(boolean triggerBack);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 9bf3b80..7749394 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -52,12 +52,13 @@
/**
* Ensures the back animation background color layer is present.
+ *
* @param startRect The start bounds of the closing target.
* @param color The background color.
* @param transaction The animation transaction.
*/
- void ensureBackground(Rect startRect, int color,
- @NonNull SurfaceControl.Transaction transaction) {
+ public void ensureBackground(
+ Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface != null) {
return;
}
@@ -81,7 +82,12 @@
mIsRequestingStatusBarAppearance = false;
}
- void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ /**
+ * Remove the back animation background.
+ *
+ * @param transaction The animation transaction.
+ */
+ public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface == null) {
return;
}
@@ -93,11 +99,21 @@
mIsRequestingStatusBarAppearance = false;
}
+ /**
+ * Attach a {@link StatusBarCustomizer} instance to allow status bar animate with back progress.
+ *
+ * @param customizer The {@link StatusBarCustomizer} to be used.
+ */
void setStatusBarCustomizer(StatusBarCustomizer customizer) {
mCustomizer = customizer;
}
- void onBackProgressed(float progress) {
+ /**
+ * Update back animation background with for the progress.
+ *
+ * @param progress Progress value from {@link android.window.BackProgressAnimator}
+ */
+ public void onBackProgressed(float progress) {
if (mCustomizer == null || mStartBounds.isEmpty()) {
return;
}
@@ -114,7 +130,14 @@
mStartBounds);
mCustomizer.customizeStatusBarAppearance(region);
} else {
- mCustomizer.customizeStatusBarAppearance(null);
+ resetStatusBarCustomization();
}
}
+
+ /**
+ * Resets the statusbar customization
+ */
+ public void resetStatusBarCustomization() {
+ mCustomizer.customizeStatusBarAppearance(null);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index bb543f2..d8c691b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.back;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
+import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -25,6 +27,7 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ContentResolver;
@@ -43,7 +46,6 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
-import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -60,6 +62,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.common.ExternalInterfaceBinder;
@@ -82,11 +85,6 @@
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
SETTING_VALUE_ON) == SETTING_VALUE_ON;
- /** Flag for U animation features */
- public static boolean IS_U_ANIMATION_ENABLED =
- SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
- SETTING_VALUE_ON) == SETTING_VALUE_ON;
-
public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
@@ -103,17 +101,22 @@
* Max duration to wait for an animation to finish before triggering the real back.
*/
private static final long MAX_ANIMATION_DURATION = 2000;
+ private final LatencyTracker mLatencyTracker;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
/** Tracks if an uninterruptible animation is in progress */
private boolean mPostCommitAnimationInProgress = false;
+
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
- /** @see #setTriggerBack(boolean) */
- private boolean mTriggerBack;
- private FlingAnimationUtils mFlingAnimationUtils;
+ private boolean mOnBackStartDispatched = false;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+
+ /** Registry for the back animations */
+ private final ShellBackAnimationRegistry mShellBackAnimationRegistry;
@Nullable
private BackNavigationInfo mBackNavigationInfo;
@@ -123,6 +126,18 @@
private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
+
+ /**
+ * Tracks the current user back gesture.
+ */
+ private TouchTracker mCurrentTracker = new TouchTracker();
+
+ /**
+ * Tracks the next back gesture in case a new user gesture has started while the back animation
+ * (and navigation) associated with {@link #mCurrentTracker} have not yet finished.
+ */
+ private TouchTracker mQueuedTracker = new TouchTracker();
+
private final Runnable mAnimationTimeoutRunnable = () -> {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
MAX_ANIMATION_DURATION);
@@ -133,15 +148,9 @@
@VisibleForTesting
BackAnimationAdapter mBackAnimationAdapter;
- private final TouchTracker mTouchTracker = new TouchTracker();
-
- private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
@Nullable
private IOnBackInvokedCallback mActiveCallback;
- private CrossActivityAnimation mDefaultActivityAnimation;
- private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
@VisibleForTesting
final RemoteCallback mNavigationObserver = new RemoteCallback(
new RemoteCallback.OnResultListener() {
@@ -155,13 +164,14 @@
}
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
setTriggerBack(false);
- onGestureFinished(false);
+ resetTouchTracker();
});
}
});
private final BackAnimationBackground mAnimationBackground;
private StatusBarCustomizer mCustomizer;
+ private boolean mTrackingLatency;
public BackAnimationController(
@NonNull ShellInit shellInit,
@@ -169,10 +179,18 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
- @NonNull BackAnimationBackground backAnimationBackground) {
- this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver(),
- backAnimationBackground);
+ @NonNull BackAnimationBackground backAnimationBackground,
+ ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ this(
+ shellInit,
+ shellController,
+ shellExecutor,
+ backgroundHandler,
+ ActivityTaskManager.getService(),
+ context,
+ context.getContentResolver(),
+ backAnimationBackground,
+ shellBackAnimationRegistry);
}
@VisibleForTesting
@@ -182,8 +200,10 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver,
- @NonNull BackAnimationBackground backAnimationBackground) {
+ Context context,
+ ContentResolver contentResolver,
+ @NonNull BackAnimationBackground backAnimationBackground,
+ ShellBackAnimationRegistry shellBackAnimationRegistry) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -197,65 +217,54 @@
.setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
- }
-
- @VisibleForTesting
- void setEnableUAnimation(boolean enable) {
- IS_U_ANIMATION_ENABLED = enable;
+ mShellBackAnimationRegistry = shellBackAnimationRegistry;
+ mLatencyTracker = LatencyTracker.getInstance(mContext);
}
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+ updateEnableAnimationFromFlags();
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
-
- initBackAnimationRunners();
- }
-
- private void initBackAnimationRunners() {
- if (!IS_U_ANIMATION_ENABLED) {
- return;
- }
-
- final CrossTaskBackAnimation crossTaskAnimation =
- new CrossTaskBackAnimation(mContext, mAnimationBackground);
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
- crossTaskAnimation.mBackAnimationRunner);
- mDefaultActivityAnimation =
- new CrossActivityAnimation(mContext, mAnimationBackground);
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mDefaultActivityAnimation.mBackAnimationRunner);
- mCustomizeActivityAnimation =
- new CustomizeActivityAnimation(mContext, mAnimationBackground);
- // TODO (236760237): register dialog close animation when it's completed.
}
private void setupAnimationDeveloperSettingsObserver(
@NonNull ContentResolver contentResolver,
@NonNull @ShellBackgroundThread final Handler backgroundHandler) {
+ if (predictiveBackSystemAnimations()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
+ + "developer settings flag is ignored and no content observer registered");
+ return;
+ }
ContentObserver settingsObserver = new ContentObserver(backgroundHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- updateEnableAnimationFromSetting();
+ updateEnableAnimationFromFlags();
}
};
contentResolver.registerContentObserver(
Global.getUriFor(Global.ENABLE_BACK_ANIMATION),
false, settingsObserver, UserHandle.USER_SYSTEM
);
- updateEnableAnimationFromSetting();
}
+ /**
+ * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the
+ * aconfig flag and the developer settings flag
+ */
@ShellBackgroundThread
- private void updateEnableAnimationFromSetting() {
- int settingValue = Global.getInt(mContext.getContentResolver(),
- Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
- boolean isEnabled = settingValue == SETTING_VALUE_ON;
+ private void updateEnableAnimationFromFlags() {
+ boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
mEnableAnimations.set(isEnabled);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
+ private boolean isDeveloperSettingEnabled() {
+ return Global.getInt(mContext.getContentResolver(),
+ Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+ }
+
public BackAnimation getBackAnimationImpl() {
return mBackAnimation;
}
@@ -296,6 +305,11 @@
}
@Override
+ public void onPilferPointers() {
+ BackAnimationController.this.onPilferPointers();
+ }
+
+ @Override
public void setTriggerBack(boolean triggerBack) {
mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack));
}
@@ -330,7 +344,11 @@
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
(controller) -> controller.registerAnimation(
BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner)));
+ new BackAnimationRunner(
+ callback,
+ runner,
+ controller.mContext,
+ CUJ_PREDICTIVE_BACK_HOME)));
}
@Override
@@ -359,11 +377,27 @@
void registerAnimation(@BackNavigationInfo.BackTargetType int type,
@NonNull BackAnimationRunner runner) {
- mAnimationDefinition.set(type, runner);
+ mShellBackAnimationRegistry.registerAnimation(type, runner);
}
void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
- mAnimationDefinition.remove(type);
+ mShellBackAnimationRegistry.unregisterAnimation(type);
+ }
+
+ private TouchTracker getActiveTracker() {
+ if (mCurrentTracker.isActive()) return mCurrentTracker;
+ if (mQueuedTracker.isActive()) return mQueuedTracker;
+ return null;
+ }
+
+ @VisibleForTesting
+ void onPilferPointers() {
+ mCurrentTracker.updateStartLocation();
+ // Dispatch onBackStarted, only to app callbacks.
+ // System callbacks will receive onBackStarted when the remote animation starts.
+ if (!shouldDispatchToAnimator()) {
+ tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+ }
}
/**
@@ -377,11 +411,19 @@
float velocityY,
int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
- if (mPostCommitAnimationInProgress) {
+
+ TouchTracker activeTouchTracker = getActiveTracker();
+ if (activeTouchTracker != null) {
+ activeTouchTracker.update(touchX, touchY, velocityX, velocityY);
+ }
+
+ // two gestures are waiting to be processed at the moment, skip any further user touches
+ if (mCurrentTracker.isFinished() && mQueuedTracker.isFinished()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "Ignoring MotionEvent because two gestures are already being queued.");
return;
}
- mTouchTracker.update(touchX, touchY, velocityX, velocityY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -399,66 +441,88 @@
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
if (keyAction == MotionEvent.ACTION_CANCEL) {
- mTriggerBack = false;
+ setTriggerBack(false);
}
- onGestureFinished(true);
+ onGestureFinished();
}
}
private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
- if (mBackGestureStarted || mBackNavigationInfo != null) {
- Log.e(TAG, "Animation is being initialized but is already started.");
- finishBackNavigation();
+ TouchTracker touchTracker;
+ if (mCurrentTracker.isInInitialState()) {
+ touchTracker = mCurrentTracker;
+ } else if (mQueuedTracker.isInInitialState()) {
+ touchTracker = mQueuedTracker;
+ } else {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW,
+ "Cannot start tracking new gesture with neither tracker in initial state.");
+ return;
}
-
- mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
+ touchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
+ touchTracker.setState(TouchTracker.TouchTrackerState.ACTIVE);
mBackGestureStarted = true;
- try {
- mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
- mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
- onBackNavigationInfoReceived(mBackNavigationInfo);
- } catch (RemoteException remoteException) {
- Log.e(TAG, "Failed to initAnimation", remoteException);
- finishBackNavigation();
+ if (touchTracker == mCurrentTracker) {
+ // Only start the back navigation if no other gesture is being processed. Otherwise,
+ // the back navigation will be started once the current gesture has finished.
+ startBackNavigation(mCurrentTracker);
}
}
- private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) {
+ private void startBackNavigation(@NonNull TouchTracker touchTracker) {
+ try {
+ startLatencyTracking();
+ mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
+ mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
+ onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker);
+ } catch (RemoteException remoteException) {
+ Log.e(TAG, "Failed to initAnimation", remoteException);
+ finishBackNavigation(touchTracker.getTriggerBack());
+ }
+ }
+
+ private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo,
+ @NonNull TouchTracker touchTracker) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
if (backNavigationInfo == null) {
- Log.e(TAG, "Received BackNavigationInfo is null.");
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
+ cancelLatencyTracking();
return;
}
final int backType = backNavigationInfo.getType();
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
- if (mAnimationDefinition.contains(backType)) {
- mAnimationDefinition.get(backType).startGesture();
- } else {
+ if (!mShellBackAnimationRegistry.startGesture(backType)) {
mActiveCallback = null;
}
} else {
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
+ // App is handling back animation. Cancel system animation latency tracking.
+ cancelLatencyTracking();
+ tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
}
}
private void onMove() {
- if (!mBackGestureStarted || mBackNavigationInfo == null || mActiveCallback == null) {
+ if (!mBackGestureStarted
+ || mBackNavigationInfo == null
+ || mActiveCallback == null
+ || !mOnBackStartDispatched) {
return;
}
-
- final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
+ // Skip dispatching if the move corresponds to the queued instead of the current gesture
+ if (mQueuedTracker.isActive()) return;
+ final BackMotionEvent backEvent = mCurrentTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backEvent);
}
private void injectBackKey() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "injectBackKey");
sendBackEvent(KeyEvent.ACTION_DOWN);
sendBackEvent(KeyEvent.ACTION_UP);
}
+ @SuppressLint("MissingPermission")
private void sendBackEvent(int action) {
final long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
@@ -469,7 +533,7 @@
ev.setDisplayId(mContext.getDisplay().getDisplayId());
if (!mContext.getSystemService(InputManager.class)
.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
- Log.e(TAG, "Inject input event fail");
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Inject input event fail");
}
}
@@ -479,13 +543,14 @@
&& mBackNavigationInfo.isPrepareRemoteAnimation();
}
- private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
+ private void tryDispatchOnBackStarted(IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (callback == null) {
+ if (callback == null || mOnBackStartDispatched) {
return;
}
try {
callback.onBackStarted(backEvent);
+ mOnBackStartDispatched = true;
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
@@ -498,7 +563,8 @@
*
* @param callback the callback to be invoked when the animation ends.
*/
- private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) {
+ private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback,
+ @NonNull TouchTracker touchTracker) {
if (callback == null) {
return;
}
@@ -507,12 +573,12 @@
if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) {
- final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backMotionEvent = touchTracker.createProgressEvent();
if (backMotionEvent != null) {
// Constraints - absolute values
float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
- float maxX = mTouchTracker.getMaxDistance(); // px
+ float maxX = touchTracker.getMaxDistance(); // px
float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px
// Current state
@@ -540,9 +606,9 @@
animator.addUpdateListener(animation -> {
Float animatedValue = (Float) animation.getAnimatedValue();
- float progress = mTouchTracker.getProgress(animatedValue);
- final BackMotionEvent backEvent = mTouchTracker
- .createProgressEvent(progress);
+ float progress = touchTracker.getProgress(animatedValue);
+ final BackMotionEvent backEvent = touchTracker.createProgressEvent(
+ progress);
dispatchOnBackProgressed(mActiveCallback, backEvent);
});
@@ -601,27 +667,27 @@
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
public void setTriggerBack(boolean triggerBack) {
- if (mPostCommitAnimationInProgress) {
- return;
+ TouchTracker activeBackGestureInfo = getActiveTracker();
+ if (activeBackGestureInfo != null) {
+ activeBackGestureInfo.setTriggerBack(triggerBack);
}
- mTriggerBack = triggerBack;
- mTouchTracker.setTriggerBack(triggerBack);
}
private void setSwipeThresholds(
float linearDistance,
float maxDistance,
float nonLinearFactor) {
- mTouchTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
+ mCurrentTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
+ mQueuedTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
}
- private void invokeOrCancelBack() {
+ private void invokeOrCancelBack(@NonNull TouchTracker touchTracker) {
// Make a synchronized call to core before dispatch back event to client side.
// If the close transition happens before the core receives onAnimationFinished, there will
// play a second close animation for that transition.
if (mBackAnimationFinishedCallback != null) {
try {
- mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
+ mBackAnimationFinishedCallback.onAnimationFinished(touchTracker.getTriggerBack());
} catch (RemoteException e) {
Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
}
@@ -630,30 +696,31 @@
if (mBackNavigationInfo != null) {
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
- if (mTriggerBack) {
- dispatchOrAnimateOnBackInvoked(callback);
+ if (touchTracker.getTriggerBack()) {
+ dispatchOrAnimateOnBackInvoked(callback, touchTracker);
} else {
dispatchOnBackCancelled(callback);
}
}
- finishBackNavigation();
+ finishBackNavigation(touchTracker.getTriggerBack());
}
/**
* Called when the gesture is released, then it could start the post commit animation.
*/
- private void onGestureFinished(boolean fromTouch) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
- if (!mBackGestureStarted) {
- finishBackNavigation();
+ private void onGestureFinished() {
+ TouchTracker activeTouchTracker = getActiveTracker();
+ if (!mBackGestureStarted || activeTouchTracker == null) {
+ // This can happen when an unfinished gesture has been reset in resetTouchTracker
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "onGestureFinished called while no gesture is started");
return;
}
+ boolean triggerBack = activeTouchTracker.getTriggerBack();
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", triggerBack);
- if (fromTouch) {
- // Let touch reset the flag otherwise it will start a new back navigation and refresh
- // the info when received a new move event.
- mBackGestureStarted = false;
- }
+ mBackGestureStarted = false;
+ activeTouchTracker.setState(TouchTracker.TouchTrackerState.FINISHED);
if (mPostCommitAnimationInProgress) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
@@ -662,30 +729,33 @@
if (mBackNavigationInfo == null) {
// No focus window found or core are running recents animation, inject back key as
- // legacy behavior.
- if (mTriggerBack) {
+ // legacy behavior, or new back gesture was started while previous has not finished yet
+ if (!mQueuedTracker.isInInitialState()) {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "mBackNavigationInfo is null AND there is "
+ + "another back animation in progress");
+ }
+ mCurrentTracker.reset();
+ if (triggerBack) {
injectBackKey();
}
- finishBackNavigation();
+ finishBackNavigation(triggerBack);
return;
}
final int backType = mBackNavigationInfo.getType();
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
// Simply trigger and finish back navigation when no animator defined.
- if (!shouldDispatchToAnimator() || runner == null) {
- invokeOrCancelBack();
+ if (!shouldDispatchToAnimator()
+ || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Trigger back without dispatching to animator.");
+ invokeOrCancelBack(mCurrentTracker);
+ mCurrentTracker.reset();
return;
- }
- if (runner.isWaitingAnimation()) {
+ } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
// Supposed it is in post commit animation state, and start the timeout to watch
// if the animation is ready.
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
return;
- } else if (runner.isAnimationCancelled()) {
- invokeOrCancelBack();
- return;
}
startPostCommitAnimation();
}
@@ -705,8 +775,8 @@
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
// The next callback should be {@link #onBackAnimationFinished}.
- if (mTriggerBack) {
- dispatchOrAnimateOnBackInvoked(mActiveCallback);
+ if (mCurrentTracker.getTriggerBack()) {
+ dispatchOrAnimateOnBackInvoked(mActiveCallback, mCurrentTracker);
} else {
dispatchOnBackCancelled(mActiveCallback);
}
@@ -724,112 +794,178 @@
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
- // Trigger the real back.
- invokeOrCancelBack();
+ if (mCurrentTracker.isActive() || mCurrentTracker.isFinished()) {
+ // Trigger the real back.
+ invokeOrCancelBack(mCurrentTracker);
+ } else {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "mCurrentBackGestureInfo was null when back animation finished");
+ }
+ resetTouchTracker();
+ }
+
+ /**
+ * Resets the TouchTracker and potentially starts a new back navigation in case one is queued
+ */
+ private void resetTouchTracker() {
+ TouchTracker temp = mCurrentTracker;
+ mCurrentTracker = mQueuedTracker;
+ temp.reset();
+ mQueuedTracker = temp;
+
+ if (mCurrentTracker.isInInitialState()) {
+ if (mBackGestureStarted) {
+ mBackGestureStarted = false;
+ dispatchOnBackCancelled(mActiveCallback);
+ finishBackNavigation(false);
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "resetTouchTracker -> reset an unfinished gesture");
+ } else {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> no queued gesture");
+ }
+ return;
+ }
+
+ if (mCurrentTracker.isFinished() && mCurrentTracker.getTriggerBack()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> start queued back navigation "
+ + "AND post commit animation");
+ injectBackKey();
+ finishBackNavigation(true);
+ mCurrentTracker.reset();
+ } else if (!mCurrentTracker.isFinished()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "resetTouchTracker -> queued gesture not finished; do nothing");
+ } else {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> reset queued gesture");
+ mCurrentTracker.reset();
+ }
}
/**
* This should be called after the whole back navigation is completed.
*/
@VisibleForTesting
- void finishBackNavigation() {
+ void finishBackNavigation(boolean triggerBack) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
- mShouldStartOnNextMoveEvent = false;
- mTouchTracker.reset();
mActiveCallback = null;
- // reset to default
- if (mDefaultActivityAnimation != null
- && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mDefaultActivityAnimation.mBackAnimationRunner);
- }
+ mShouldStartOnNextMoveEvent = false;
+ mOnBackStartDispatched = false;
+ mShellBackAnimationRegistry.resetDefaultCrossActivity();
+ cancelLatencyTracking();
if (mBackNavigationInfo != null) {
- mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+ mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
}
- mTriggerBack = false;
}
- private BackAnimationRunner getAnimationRunnerAndInit() {
- int type = mBackNavigationInfo.getType();
- // Initiate customized cross-activity animation, or fall back to cross activity animation
- if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
- final BackNavigationInfo.CustomAnimationInfo animationInfo =
- mBackNavigationInfo.getCustomAnimationInfo();
- if (animationInfo != null && mCustomizeActivityAnimation != null
- && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
- mAnimationDefinition.get(type).resetWaitingAnimation();
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mCustomizeActivityAnimation.mBackAnimationRunner);
- }
+ private void startLatencyTracking() {
+ if (mTrackingLatency) {
+ cancelLatencyTracking();
}
- return mAnimationDefinition.get(type);
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
+ mTrackingLatency = true;
+ }
+
+ private void cancelLatencyTracking() {
+ if (!mTrackingLatency) {
+ return;
+ }
+ mLatencyTracker.onActionCancel(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
+ mTrackingLatency = false;
+ }
+
+ private void endLatencyTracking() {
+ if (!mTrackingLatency) {
+ return;
+ }
+ mLatencyTracker.onActionEnd(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
+ mTrackingLatency = false;
}
private void createAdapter() {
- IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IBackAnimationFinishedCallback finishedCallback) {
- mShellExecutor.execute(() -> {
- if (mBackNavigationInfo == null) {
- Log.e(TAG, "Lack of navigation info to start animation.");
- return;
- }
- final int type = mBackNavigationInfo.getType();
- final BackAnimationRunner runner = getAnimationRunnerAndInit();
- if (runner == null) {
- Log.e(TAG, "Animation didn't be defined for type "
- + BackNavigationInfo.typeToString(type));
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished(false);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call IBackNaviAnimationController", e);
- }
- }
- return;
- }
- mActiveCallback = runner.getCallback();
- mBackAnimationFinishedCallback = finishedCallback;
+ IBackAnimationRunner runner =
+ new IBackAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IBackAnimationFinishedCallback finishedCallback) {
+ mShellExecutor.execute(
+ () -> {
+ endLatencyTracking();
+ if (mBackNavigationInfo == null) {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW,
+ "Lack of navigation info to start animation.");
+ return;
+ }
+ final BackAnimationRunner runner =
+ mShellBackAnimationRegistry.getAnimationRunnerAndInit(
+ mBackNavigationInfo);
+ if (runner == null) {
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished(false);
+ } catch (RemoteException e) {
+ Log.w(
+ TAG,
+ "Failed call IBackNaviAnimationController",
+ e);
+ }
+ }
+ return;
+ }
+ mActiveCallback = runner.getCallback();
+ mBackAnimationFinishedCallback = finishedCallback;
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
- runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
- BackAnimationController.this::onBackAnimationFinished));
+ ProtoLog.d(
+ WM_SHELL_BACK_PREVIEW,
+ "BackAnimationController: startAnimation()");
+ runner.startAnimation(
+ apps,
+ wallpapers,
+ nonApps,
+ () ->
+ mShellExecutor.execute(
+ BackAnimationController.this
+ ::onBackAnimationFinished));
- if (apps.length >= 1) {
- dispatchOnBackStarted(
- mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
+ if (apps.length >= 1) {
+ mCurrentTracker.updateStartLocation();
+ tryDispatchOnBackStarted(
+ mActiveCallback,
+ mCurrentTracker.createStartEvent(apps[0]));
+ }
+
+ // Dispatch the first progress after animation start for
+ // smoothing the initial animation, instead of waiting for next
+ // onMove.
+ final BackMotionEvent backFinish = mCurrentTracker
+ .createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ if (!mBackGestureStarted) {
+ // if the down -> up gesture happened before animation
+ // start, we have to trigger the uninterruptible transition
+ // to finish the back animation.
+ startPostCommitAnimation();
+ }
+ });
}
- // Dispatch the first progress after animation start for smoothing the initial
- // animation, instead of waiting for next onMove.
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
- // if the down -> up gesture happened before animation start, we have to
- // trigger the uninterruptible transition to finish the back animation.
- startPostCommitAnimation();
+ @Override
+ public void onAnimationCancelled() {
+ mShellExecutor.execute(
+ () -> {
+ if (!mShellBackAnimationRegistry.cancel(
+ mBackNavigationInfo.getType())) {
+ return;
+ }
+ if (!mBackGestureStarted) {
+ invokeOrCancelBack(mCurrentTracker);
+ }
+ });
}
- });
- }
-
- @Override
- public void onAnimationCancelled() {
- mShellExecutor.execute(() -> {
- final BackAnimationRunner runner = mAnimationDefinition.get(
- mBackNavigationInfo.getType());
- if (runner == null) {
- return;
- }
- runner.cancelAnimation();
- if (!mBackGestureStarted) {
- invokeOrCancelBack();
- }
- });
- }
- };
+ };
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 913239f7..a32b435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import android.annotation.NonNull;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -27,16 +28,22 @@
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.jank.Cuj.CujType;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
+
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
-class BackAnimationRunner {
+public class BackAnimationRunner {
+ private static final int NO_CUJ = -1;
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
+ private final @CujType int mCujType;
+ private final Context mContext;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -44,10 +51,22 @@
/** True when the back animation is cancelled */
private boolean mAnimationCancelled;
- BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
- @NonNull IRemoteAnimationRunner runner) {
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context,
+ @CujType int cujType) {
mCallback = callback;
mRunner = runner;
+ mCujType = cujType;
+ mContext = context;
+ }
+
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context) {
+ this(callback, runner, context, NO_CUJ);
}
/** Returns the registered animation runner */
@@ -70,10 +89,17 @@
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.endTracing(mCujType);
+ }
finishedCallback.run();
}
};
mWaitingAnimation = false;
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.beginTracing(
+ mCujType, mContext, apps[0].leash, /* tag */ null);
+ }
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
@@ -82,6 +108,10 @@
}
}
+ private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ return apps.length > 0 && mCujType != NO_CUJ;
+ }
+
void startGesture() {
mWaitingAnimation = true;
mAnimationCancelled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
similarity index 91%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 74a243d..215a6cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -19,7 +19,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
+import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -51,9 +53,11 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import javax.inject.Inject;
+
/** Class that defines cross-activity animation. */
@ShellMainThread
-class CrossActivityAnimation {
+public class CrossActivityBackAnimation extends ShellBackAnimation {
/**
* Minimum scale of the entering/closing window.
*/
@@ -62,27 +66,27 @@
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
- private static final FloatProperty<CrossActivityAnimation> ENTER_PROGRESS_PROP =
+ private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP =
new FloatProperty<>("enter-alpha") {
@Override
- public void setValue(CrossActivityAnimation anim, float value) {
+ public void setValue(CrossActivityBackAnimation anim, float value) {
anim.setEnteringProgress(value);
}
@Override
- public Float get(CrossActivityAnimation object) {
+ public Float get(CrossActivityBackAnimation object) {
return object.getEnteringProgress();
}
};
- private static final FloatProperty<CrossActivityAnimation> LEAVE_PROGRESS_PROP =
+ private static final FloatProperty<CrossActivityBackAnimation> LEAVE_PROGRESS_PROP =
new FloatProperty<>("leave-alpha") {
@Override
- public void setValue(CrossActivityAnimation anim, float value) {
+ public void setValue(CrossActivityBackAnimation anim, float value) {
anim.setLeavingProgress(value);
}
@Override
- public Float get(CrossActivityAnimation object) {
+ public Float get(CrossActivityBackAnimation object) {
return object.getLeavingProgress();
}
};
@@ -106,6 +110,7 @@
private final SpringAnimation mLeavingProgressSpring;
// Max window x-shift in pixels.
private final float mWindowXShift;
+ private final BackAnimationRunner mBackAnimationRunner;
private float mEnteringProgress = 0f;
private float mLeavingProgress = 0f;
@@ -126,13 +131,14 @@
private IRemoteAnimationFinishedCallback mFinishCallback;
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
private final BackAnimationBackground mBackground;
- CrossActivityAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CrossActivityBackAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mBackground = background;
mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
mEnteringProgressSpring.setSpring(new SpringForce()
@@ -186,6 +192,8 @@
// Draw background with task background color.
mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ setEnteringProgress(0);
+ setLeavingProgress(0);
}
private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
@@ -267,12 +275,16 @@
valueAnimator.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
updatePostCommitEnteringAnimation(progress);
+ if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ mBackground.resetStatusBarCustomization();
+ }
mTransaction.apply();
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ mBackground.resetStatusBarCustomization();
finishAnimation();
}
});
@@ -348,7 +360,6 @@
closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
// Move the window along the Y axis.
- final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
final float closingTop = (height - closingHeight) * 0.5f;
targetRect.set(
closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
@@ -357,11 +368,16 @@
mTransaction.apply();
}
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
- CrossActivityAnimation.this::onGestureProgress);
+ CrossActivityBackAnimation.this::onGestureProgress);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a7dd27a..80fc3a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -20,6 +20,8 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.window.BackEvent.EDGE_RIGHT;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
+import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -36,7 +38,7 @@
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -45,52 +47,42 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import javax.inject.Inject;
+
/**
* Controls the animation of swiping back and returning to another task.
*
- * This is a two part animation. The first part is an animation that tracks gesture location to
- * scale and move the closing and entering app windows.
- * Once the gesture is committed, the second part remains the closing window in place.
- * The entering window plays the rest of app opening transition to enter full screen.
+ * <p>This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows. Once the gesture is committed, the second
+ * part remains the closing window in place. The entering window plays the rest of app opening
+ * transition to enter full screen.
*
- * This animation is used only for apps that enable back dispatching via
- * {@link android.window.OnBackInvokedDispatcher}. The controller registers
- * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
- * navigation to launcher starts.
+ * <p>This animation is used only for apps that enable back dispatching via {@link
+ * android.window.OnBackInvokedDispatcher}. The controller registers an {@link
+ * IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
+ * launcher starts.
*/
@ShellMainThread
-class CrossTaskBackAnimation {
+public class CrossTaskBackAnimation extends ShellBackAnimation {
private static final int BACKGROUNDCOLOR = 0x43433A;
/**
- * Minimum scale of the entering window.
+ * Minimum scale of the entering and closing window.
*/
- private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+ private static final float MIN_WINDOW_SCALE = 0.8f;
- /**
- * Minimum scale of the closing window.
- */
- private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
-
- /**
- * Minimum color scale of the closing window.
- */
- private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
-
- /**
- * The margin between the entering window and the closing window
- */
- private static final int WINDOW_MARGIN = 35;
-
- /** Max window translation in the Y axis. */
- private static final int WINDOW_MAX_DELTA_Y = 160;
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION_MS = 500;
private final Rect mStartTaskRect = new Rect();
private final float mCornerRadius;
// The closing window properties.
+ private final Rect mClosingStartRect = new Rect();
private final RectF mClosingCurrentRect = new RectF();
// The entering window properties.
@@ -98,36 +90,40 @@
private final RectF mEnteringCurrentRect = new RectF();
private final PointF mInitialTouchPos = new PointF();
- private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
-
+ private final Interpolator mPostAnimationInterpolator = Interpolators.EMPHASIZED;
+ private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
private final Matrix mTransformMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
- private final float[] mTmpTranslate = {0, 0, 0};
-
+ private final BackAnimationRunner mBackAnimationRunner;
+ private final BackAnimationBackground mBackground;
+ private final Context mContext;
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
- private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
-
private boolean mIsRightEdge;
- private float mProgress = 0;
- private PointF mTouchPos = new PointF();
+ private final PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
- private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ private float mInterWindowMargin;
+ private float mVerticalMargin;
- private final BackAnimationBackground mBackground;
-
- CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
mBackground = background;
+ mContext = context;
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
}
private float getInterpolatedProgress(float backProgress) {
- return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+ return mProgressInterpolator.getInterpolation(backProgress);
}
private void startBackAnimation() {
@@ -143,6 +139,10 @@
// Draw background.
mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
BACKGROUNDCOLOR, mTransaction);
+ mInterWindowMargin = mContext.getResources()
+ .getDimension(R.dimen.cross_task_back_inter_window_margin);
+ mVerticalMargin = mContext.getResources()
+ .getDimension(R.dimen.cross_task_back_vertical_margin);
}
private void updateGestureBackProgress(float progress, BackEvent event) {
@@ -150,44 +150,43 @@
return;
}
- float touchX = event.getTouchX();
float touchY = event.getTouchY();
- float dX = Math.abs(touchX - mInitialTouchPos.x);
// The 'follow width' is the width of the window if it completely matches
// the gesture displacement.
final int width = mStartTaskRect.width();
final int height = mStartTaskRect.height();
- // The 'progress width' is the width of the window if it strictly linearly interpolates
- // to minimum scale base on progress.
- float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
- float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
- float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+ float scale = mapRange(progress, 1, MIN_WINDOW_SCALE);
+ float scaledWidth = scale * width;
+ float scaledHeight = scale * height;
- // The final width is derived from interpolating between the follow with and progress width
- // using gesture progress.
- float enteringWidth = enteringScale * width;
- float closingWidth = closingScale * width;
- float enteringHeight = (float) height / width * enteringWidth;
- float closingHeight = (float) height / width * closingWidth;
-
- float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
// Base the window movement in the Y axis on the touch movement in the Y axis.
- float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
- // Move the window along the Y axis.
- float closingTop = (height - closingHeight) * 0.5f + deltaY;
- float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
- // Move the window along the X axis.
- float right = width - (progress * WINDOW_MARGIN);
- float left = right - closingWidth;
+ float rawYDelta = touchY - mInitialTouchPos.y;
+ float yDirection = rawYDelta < 0 ? -1 : 1;
+ // limit yDelta interpretation to 1/2 of screen height in either direction
+ float deltaYRatio = Math.min(height / 2f, Math.abs(rawYDelta)) / (height / 2f);
+ float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
+ // limit y-shift so surface never passes 8dp screen margin
+ float deltaY = yDirection * interpolatedYRatio * Math.max(0f,
+ (height - scaledHeight) / 2f - mVerticalMargin);
- mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
- mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
- left - WINDOW_MARGIN, enteringTop + enteringHeight);
+ // Move the window along the Y axis.
+ float scaledTop = (height - scaledHeight) * 0.5f + deltaY;
+ // Move the window along the X axis.
+ float right;
+ if (mIsRightEdge) {
+ right = (width - scaledWidth) * 0.5f + scaledWidth;
+ } else {
+ right = width - (progress * mVerticalMargin);
+ }
+ float left = right - scaledWidth;
+
+ mClosingCurrentRect.set(left, scaledTop, right, scaledTop + scaledHeight);
+ mEnteringCurrentRect.set(left - scaledWidth - mInterWindowMargin, scaledTop,
+ left - mInterWindowMargin, scaledTop + scaledHeight);
applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
- applyColorTransform(mClosingTarget.leash, closingColorScale);
applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
mTransaction.apply();
@@ -195,9 +194,21 @@
}
private void updatePostCommitClosingAnimation(float progress) {
+ float targetLeft =
+ mStartTaskRect.left + (1 - MIN_WINDOW_SCALE) * mStartTaskRect.width() / 2;
+ float targetTop =
+ mStartTaskRect.top + (1 - MIN_WINDOW_SCALE) * mStartTaskRect.height() / 2;
+ float targetWidth = mStartTaskRect.width() * MIN_WINDOW_SCALE;
+ float targetHeight = mStartTaskRect.height() * MIN_WINDOW_SCALE;
+
+ float left = mapRange(progress, mClosingStartRect.left, targetLeft);
+ float top = mapRange(progress, mClosingStartRect.top, targetTop);
+ float width = mapRange(progress, mClosingStartRect.width(), targetWidth);
+ float height = mapRange(progress, mClosingStartRect.height(), targetHeight);
mTransaction.setLayer(mClosingTarget.leash, 0);
- float alpha = mapRange(progress, 1, 0);
- mTransaction.setAlpha(mClosingTarget.leash, alpha);
+
+ mClosingCurrentRect.set(left, top, left + width, top + height);
+ applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
}
private void updatePostCommitEnteringAnimation(float progress) {
@@ -225,26 +236,6 @@
.setCornerRadius(leash, cornerRadius);
}
- private void applyColorTransform(SurfaceControl leash, float colorScale) {
- if (leash == null) {
- return;
- }
- computeScaleTransformMatrix(colorScale, mTmpFloat9);
- mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
- }
-
- static void computeScaleTransformMatrix(float scale, float[] matrix) {
- matrix[0] = scale;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = scale;
- matrix[5] = 0;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = scale;
- }
-
private void finishAnimation() {
if (mEnteringTarget != null) {
mEnteringTarget.leash.release();
@@ -269,7 +260,8 @@
try {
mFinishCallback.onAnimationFinished();
} catch (RemoteException e) {
- e.printStackTrace();
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW,
+ "RemoteException when invoking onAnimationFinished callback");
}
mFinishCallback = null;
}
@@ -277,13 +269,13 @@
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
- mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mBackInProgress = true;
}
- mProgress = backEvent.getProgress();
+ float progress = backEvent.getProgress();
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
- updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+ updateGestureBackProgress(getInterpolatedProgress(progress), backEvent);
}
private void onGestureCommitted() {
@@ -295,30 +287,37 @@
// We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
// coordinate of the gesture driven phase.
mEnteringCurrentRect.round(mEnteringStartRect);
+ mClosingCurrentRect.round(mClosingStartRect);
- ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
- valueAnimator.setInterpolator(mInterpolator);
+ ValueAnimator valueAnimator =
+ ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION_MS);
+ valueAnimator.setInterpolator(mPostAnimationInterpolator);
valueAnimator.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
updatePostCommitEnteringAnimation(progress);
updatePostCommitClosingAnimation(progress);
+ if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ mBackground.resetStatusBarCustomization();
+ }
mTransaction.apply();
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ mBackground.resetStatusBarCustomization();
finishAnimation();
}
});
valueAnimator.start();
}
- private static float mapRange(float value, float min, float max) {
- return min + (value * (max - min));
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
}
- private final class Callback extends IOnBackInvokedCallback.Default {
+ private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
@@ -340,7 +339,7 @@
mProgressAnimator.reset();
onGestureCommitted();
}
- };
+ }
private final class Runner extends IRemoteAnimationRunner.Default {
@Override
@@ -360,5 +359,5 @@
startBackAnimation();
mFinishCallback = finishedCallback;
}
- };
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index 2d6ec75..5254ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -19,6 +19,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -55,13 +56,13 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
-/**
- * Class that handle customized close activity transition animation.
- */
+import javax.inject.Inject;
+
+/** Class that handle customized close activity transition animation. */
@ShellMainThread
-class CustomizeActivityAnimation {
+public class CustomizeActivityAnimation extends ShellBackAnimation {
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
+ private final BackAnimationRunner mBackAnimationRunner;
private final float mCornerRadius;
private final SurfaceControl.Transaction mTransaction;
private final BackAnimationBackground mBackground;
@@ -88,7 +89,8 @@
private final Choreographer mChoreographer;
- CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
this(context, background, new SurfaceControl.Transaction(), null);
}
@@ -96,7 +98,8 @@
SurfaceControl.Transaction transaction, Choreographer choreographer) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackground = background;
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mCustomAnimationLoader = new CustomAnimationLoader(context);
mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
@@ -258,10 +261,12 @@
valueAnimator.start();
}
- /**
- * Load customize animation before animation start.
- */
- boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ /** Load customize animation before animation start. */
+ @Override
+ public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ if (animationInfo == null) {
+ return false;
+ }
final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
if (result != null) {
mCloseAnimation = result.mCloseAnimation;
@@ -272,6 +277,11 @@
return false;
}
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
new file mode 100644
index 0000000..dc65919
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.back;
+
+import android.window.BackNavigationInfo;
+
+import javax.inject.Qualifier;
+
+/** Base class for all back animations. */
+public abstract class ShellBackAnimation {
+ @Qualifier
+ public @interface CrossActivity {}
+
+ @Qualifier
+ public @interface CrossTask {}
+
+ @Qualifier
+ public @interface CustomizeActivity {}
+
+ @Qualifier
+ public @interface ReturnToHome {}
+
+ @Qualifier
+ public @interface DialogClose {}
+
+ /** Retrieve the {@link BackAnimationRunner} associated with this animation. */
+ public abstract BackAnimationRunner getRunner();
+
+ /**
+ * Prepare the next animation with customized animation.
+ *
+ * @return true if this type of back animation should override the default.
+ */
+ public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
new file mode 100644
index 0000000..26d2097
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 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.back;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.window.BackNavigationInfo;
+
+/** Registry for all types of default back animations */
+public class ShellBackAnimationRegistry {
+ private static final String TAG = "ShellBackPreview";
+
+ private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+ private final ShellBackAnimation mDefaultCrossActivityAnimation;
+ private final ShellBackAnimation mCustomizeActivityAnimation;
+
+ public ShellBackAnimationRegistry(
+ @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
+ @ShellBackAnimation.CrossTask @Nullable ShellBackAnimation crossTaskAnimation,
+ @ShellBackAnimation.DialogClose @Nullable ShellBackAnimation dialogCloseAnimation,
+ @ShellBackAnimation.CustomizeActivity @Nullable
+ ShellBackAnimation customizeActivityAnimation,
+ @ShellBackAnimation.ReturnToHome @Nullable
+ ShellBackAnimation defaultBackToHomeAnimation) {
+ if (crossActivityAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, crossActivityAnimation.getRunner());
+ }
+ if (crossTaskAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_TASK, crossTaskAnimation.getRunner());
+ }
+ if (dialogCloseAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_DIALOG_CLOSE, dialogCloseAnimation.getRunner());
+ }
+ if (defaultBackToHomeAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, defaultBackToHomeAnimation.getRunner());
+ }
+
+ mDefaultCrossActivityAnimation = crossActivityAnimation;
+ mCustomizeActivityAnimation = customizeActivityAnimation;
+
+ // TODO(b/236760237): register dialog close animation when it's completed.
+ }
+
+ void registerAnimation(
+ @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) {
+ mAnimationDefinition.set(type, runner);
+ }
+
+ void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+ mAnimationDefinition.remove(type);
+ }
+
+ /**
+ * Start the {@link BackAnimationRunner} associated with a back target type.
+ *
+ * @param type back target type
+ * @return true if the animation is started, false if animation is not found for that type.
+ */
+ boolean startGesture(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ runner.startGesture();
+ return true;
+ }
+
+ /**
+ * Cancel the {@link BackAnimationRunner} associated with a back target type.
+ *
+ * @param type back target type
+ * @return true if the animation is started, false if animation is not found for that type.
+ */
+ boolean cancel(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ runner.cancelAnimation();
+ return true;
+ }
+
+ boolean isAnimationCancelledOrNull(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return true;
+ }
+ return runner.isAnimationCancelled();
+ }
+
+ boolean isWaitingAnimation(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ return runner.isWaitingAnimation();
+ }
+
+ void resetDefaultCrossActivity() {
+ if (mDefaultCrossActivityAnimation == null
+ || !mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ return;
+ }
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
+ }
+
+ BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
+ int type = backNavigationInfo.getType();
+ // Initiate customized cross-activity animation, or fall back to cross activity animation
+ if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+ if (mCustomizeActivityAnimation != null
+ && mCustomizeActivityAnimation.prepareNextAnimation(
+ backNavigationInfo.getCustomAnimationInfo())) {
+ mAnimationDefinition.get(type).resetWaitingAnimation();
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCustomizeActivityAnimation.getRunner());
+ }
+ }
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ Log.e(
+ TAG,
+ "Animation didn't be defined for type "
+ + BackNavigationInfo.typeToString(type));
+ }
+ return runner;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
index 837d5ff..f02559f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -12,19 +12,19 @@
]
},
{
- "name": "CtsWindowManagerDeviceTestCases",
+ "name": "CtsWindowManagerDeviceBackNavigation",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
- "include-filter": "android.server.wm.BackGestureInvokedTest"
+ "include-filter": "android.server.wm.backnavigation.BackGestureInvokedTest"
},
{
- "include-filter": "android.server.wm.BackNavigationTests"
+ "include-filter": "android.server.wm.backnavigation.BackNavigationTests"
},
{
- "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest"
+ "include-filter": "android.server.wm.backnavigation.OnBackInvokedCallbackGestureTest"
}
]
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index a0ada39..4bd56d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -51,16 +51,15 @@
private float mLatestVelocityY;
private float mStartThresholdX;
private int mSwipeEdge;
- private boolean mCancelled;
+ private TouchTrackerState mState = TouchTrackerState.INITIAL;
void update(float touchX, float touchY, float velocityX, float velocityY) {
/**
* If back was previously cancelled but the user has started swiping in the forward
* direction again, restart back.
*/
- if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT)
- || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
- mCancelled = false;
+ if ((touchX < mStartThresholdX && mSwipeEdge == BackEvent.EDGE_LEFT)
+ || (touchX > mStartThresholdX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
mStartThresholdX = touchX;
}
mLatestTouchX = touchX;
@@ -71,24 +70,55 @@
void setTriggerBack(boolean triggerBack) {
if (mTriggerBack != triggerBack && !triggerBack) {
- mCancelled = true;
+ mStartThresholdX = mLatestTouchX;
}
mTriggerBack = triggerBack;
}
+ boolean getTriggerBack() {
+ return mTriggerBack;
+ }
+
+ void setState(TouchTrackerState state) {
+ mState = state;
+ }
+
+ boolean isInInitialState() {
+ return mState == TouchTrackerState.INITIAL;
+ }
+
+ boolean isActive() {
+ return mState == TouchTrackerState.ACTIVE;
+ }
+
+ boolean isFinished() {
+ return mState == TouchTrackerState.FINISHED;
+ }
+
void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
mInitTouchX = touchX;
mInitTouchY = touchY;
+ mLatestTouchX = touchX;
+ mLatestTouchY = touchY;
mSwipeEdge = swipeEdge;
mStartThresholdX = mInitTouchX;
}
+ /** Update the start location used to compute the progress
+ * to the latest touch location.
+ */
+ void updateStartLocation() {
+ mInitTouchX = mLatestTouchX;
+ mInitTouchY = mLatestTouchY;
+ mStartThresholdX = mInitTouchX;
+ }
+
void reset() {
mInitTouchX = 0;
mInitTouchY = 0;
mStartThresholdX = 0;
- mCancelled = false;
mTriggerBack = false;
+ mState = TouchTrackerState.INITIAL;
mSwipeEdge = BackEvent.EDGE_LEFT;
}
@@ -104,11 +134,7 @@
}
BackMotionEvent createProgressEvent() {
- float progress = 0;
- // Progress is always 0 when back is cancelled and not restarted.
- if (!mCancelled) {
- progress = getProgress(mLatestTouchX);
- }
+ float progress = getProgress(mLatestTouchX);
return createProgressEvent(progress);
}
@@ -126,7 +152,13 @@
// The starting threshold is initially the first touch location, and updated to
// the location everytime back is restarted after being cancelled.
float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
- float deltaX = Math.abs(startX - touchX);
+ float distance;
+ if (mSwipeEdge == BackEvent.EDGE_LEFT) {
+ distance = touchX - startX;
+ } else {
+ distance = startX - touchX;
+ }
+ float deltaX = Math.max(0f, distance);
float linearDistance = mLinearDistance;
float maxDistance = getMaxDistance();
maxDistance = maxDistance == 0 ? 1 : maxDistance;
@@ -186,4 +218,9 @@
mMaxDistance = maxDistance;
mNonLinearFactor = nonLinearFactor;
}
+
+ enum TouchTrackerState {
+ INITIAL, ACTIVE, FINISHED
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index f1ee8fa..a67821b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -318,7 +318,7 @@
/**
* Animates the dot to the given scale, running the optional callback when the animation ends.
*/
- private void animateDotScale(float toScale, @Nullable Runnable after) {
+ public void animateDotScale(float toScale, @Nullable Runnable after) {
mDotIsAnimating = true;
// Don't restart the animation if we're already animating to the given value.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 9a2b812..7a3210e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -637,7 +637,7 @@
* @return the last time this bubble was updated or accessed, whichever is most recent.
*/
long getLastActivity() {
- return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed);
+ return Math.max(mLastUpdated, mLastAccessed);
}
/**
@@ -973,9 +973,9 @@
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
pw.print(" isDismissable: "); pw.println(mIsDismissable);
- pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
+ pw.println(" bubbleMetadataFlagListener null?: " + (mBubbleMetadataFlagListener == null));
if (mExpandedView != null) {
- mExpandedView.dump(pw);
+ mExpandedView.dump(pw, " ");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 8400dde..249f52b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.bubbles;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -57,6 +56,7 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Binder;
@@ -115,6 +115,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -143,6 +144,8 @@
// Should match with PhoneWindowManager
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
/**
* Common interface to send updates to bubble views.
@@ -182,6 +185,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
private final TaskViewTransitions mTaskViewTransitions;
+ private final Transitions mTransitions;
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
@@ -282,6 +286,7 @@
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService,
BubbleProperties bubbleProperties) {
@@ -312,11 +317,13 @@
mBubbleIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
+ mTransitions = transitions;
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
@@ -416,23 +423,9 @@
}
}, mMainHandler);
- mTaskStackListener.addListener(new TaskStackListenerCallback() {
- @Override
- public void onTaskMovedToFront(int taskId) {
- mMainExecutor.execute(() -> {
- int expandedId = INVALID_TASK_ID;
- if (mStackView != null && mStackView.getExpandedBubble() != null
- && isStackExpanded()
- && !mStackView.isExpansionAnimating()
- && !mStackView.isSwitchAnimating()) {
- expandedId = mStackView.getExpandedBubble().getTaskId();
- }
- if (expandedId != INVALID_TASK_ID && expandedId != taskId) {
- mBubbleData.setExpanded(false);
- }
- });
- }
+ mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));
+ mTaskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
@@ -520,6 +513,7 @@
* <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
*/
public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+ mBubbleProperties.refresh();
if (canShowAsBubbleBar() && listener != null) {
// Only set the listener if we can show the bubble bar.
mBubbleStateListener = listener;
@@ -537,6 +531,7 @@
* will be updated accordingly.
*/
public void unregisterBubbleStateListener() {
+ mBubbleProperties.refresh();
if (mBubbleStateListener != null) {
mBubbleStateListener = null;
setUpBubbleViewsForMode();
@@ -558,8 +553,9 @@
* Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
*/
void hideCurrentInputMethod() {
+ int displayId = mWindowManager.getDefaultDisplay().getDisplayId();
try {
- mBarService.hideCurrentInputMethodForBubbles();
+ mBarService.hideCurrentInputMethodForBubbles(displayId);
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -792,7 +788,7 @@
mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
mWindowInsets = windowInsets;
- mBubblePositioner.update();
+ mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
mLayerView.onDisplaySizeChanged();
}
return windowInsets;
@@ -802,7 +798,7 @@
mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
mWindowInsets = windowInsets;
- mBubblePositioner.update();
+ mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
mStackView.onDisplaySizeChanged();
}
return windowInsets;
@@ -883,8 +879,10 @@
String action = intent.getAction();
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
- if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason))
+ boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)
+ || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)
+ || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason);
+ if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse)
|| Intent.ACTION_SCREEN_OFF.equals(action)) {
mMainExecutor.execute(() -> collapseStack());
}
@@ -953,7 +951,8 @@
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
@@ -981,7 +980,7 @@
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mBubblePositioner != null) {
- mBubblePositioner.update();
+ mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
}
if (mStackView != null && newConfig != null) {
if (newConfig.densityDpi != mDensityDpi
@@ -992,7 +991,8 @@
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
@@ -1070,6 +1070,15 @@
}
}
+ /**
+ * Show bubble bar user education relative to the reference position.
+ * @param position the reference position in Screen coordinates.
+ */
+ public void showUserEducation(Point position) {
+ if (mLayerView == null) return;
+ mLayerView.showUserEducation(position);
+ }
+
@VisibleForTesting
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1122,6 +1131,16 @@
}
/**
+ * Expands the stack if the selected bubble is present. This is currently used when user
+ * education view is clicked to expand the selected bubble.
+ */
+ public void expandStackWithSelectedBubble() {
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleData.setExpanded(true);
+ }
+ }
+
+ /**
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow. This is currently used when opening a bubble via clicking on a conversation widget.
*/
@@ -1261,7 +1280,14 @@
* Dismiss bubble if it exists and remove it from the stack
*/
public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
- mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
+ dismissBubble(bubble.getKey(), reason);
+ }
+
+ /**
+ * Dismiss bubble with given key if it exists and remove it from the stack
+ */
+ public void dismissBubble(String key, @Bubbles.DismissReason int reason) {
+ mBubbleData.dismissBubbleWithKey(key, reason);
}
/**
@@ -1737,7 +1763,8 @@
+ " expandedChanged=" + update.expandedChanged
+ " selectionChanged=" + update.selectionChanged
+ " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null));
+ + " unsuppressed=" + (update.unsuppressedBubble != null)
+ + " shouldShowEducation=" + update.shouldShowEducation);
}
ensureBubbleViewsAndWindowCreated();
@@ -1961,6 +1988,15 @@
}
}
+ /**
+ * Returns whether the stack is animating or not.
+ */
+ public boolean isStackAnimating() {
+ return mStackView != null
+ && (mStackView.isExpansionAnimating()
+ || mStackView.isSwitchAnimating());
+ }
+
@VisibleForTesting
@Nullable
public BubbleStackView getStackView() {
@@ -1989,13 +2025,20 @@
* Description of current bubble state.
*/
private void dump(PrintWriter pw, String prefix) {
- pw.println("BubbleController state:");
+ pw.print(prefix); pw.println("BubbleController state:");
+ pw.print(prefix); pw.println(" currentUserId= " + mCurrentUserId);
+ pw.print(prefix); pw.println(" isStatusBarShade= " + mIsStatusBarShade);
+ pw.print(prefix); pw.println(" isShowingAsBubbleBar= " + isShowingAsBubbleBar());
+ pw.println();
+
mBubbleData.dump(pw);
pw.println();
+
if (mStackView != null) {
mStackView.dump(pw);
}
pw.println();
+
mImpl.mCachedState.dump(pw);
}
@@ -2146,6 +2189,12 @@
public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
}
+
+ @Override
+ public void showUserEducation(int positionX, int positionY) {
+ mMainExecutor.execute(() ->
+ mController.showUserEducation(new Point(positionX, positionY)));
+ }
}
private class BubblesImpl implements Bubbles {
@@ -2244,8 +2293,7 @@
pw.println("mIsStackExpanded: " + mIsStackExpanded);
pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey);
- pw.print("mSuppressedBubbleKeys: ");
- pw.println(mSuppressedBubbleKeys.size());
+ pw.println("mSuppressedBubbleKeys: " + mSuppressedBubbleKeys.size());
for (String key : mSuppressedBubbleKeys) {
pw.println(" suppressing: " + key);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index cc8f50e..bbb4b74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -77,6 +77,7 @@
boolean orderChanged;
boolean suppressedSummaryChanged;
boolean expanded;
+ boolean shouldShowEducation;
@Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@@ -126,6 +127,7 @@
bubbleBarUpdate.expandedChanged = expandedChanged;
bubbleBarUpdate.expanded = expanded;
+ bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
if (selectionChanged) {
bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
? selectedBubble.getKey()
@@ -165,6 +167,7 @@
*/
BubbleBarUpdate getInitialState() {
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
for (int i = 0; i < bubbles.size(); i++) {
bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
}
@@ -187,6 +190,7 @@
private final Context mContext;
private final BubblePositioner mPositioner;
+ private final BubbleEducationController mEducationController;
private final Executor mMainExecutor;
/** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
@@ -233,10 +237,11 @@
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
- Executor mainExecutor) {
+ BubbleEducationController educationController, Executor mainExecutor) {
mContext = context;
mLogger = bubbleLogger;
mPositioner = positioner;
+ mEducationController = educationController;
mMainExecutor = mainExecutor;
mOverflow = new BubbleOverflow(context, positioner);
mBubbles = new ArrayList<>();
@@ -447,6 +452,7 @@
if (bubble.shouldAutoExpand()) {
bubble.setShouldAutoExpand(false);
setSelectedBubbleInternal(bubble);
+
if (!mExpanded) {
setExpandedInternal(true);
}
@@ -778,8 +784,7 @@
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
|| reason == Bubbles.DISMISS_USER_GESTURE
- || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
- || bubble.isAppBubble()) {
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
if (DEBUG_BUBBLE_DATA) {
@@ -877,6 +882,9 @@
private void dispatchPendingChanges() {
if (mListener != null && mStateChange.anythingChanged()) {
+ mStateChange.shouldShowEducation = mSelectedBubble != null
+ && mEducationController.shouldShowStackEducation(mSelectedBubble)
+ && !mExpanded;
mListener.applyUpdate(mStateChange);
}
mStateChange = new Update(mBubbles, mOverflowBubbles);
@@ -1231,29 +1239,30 @@
* Description of current bubble data state.
*/
public void dump(PrintWriter pw) {
- pw.print("selected: ");
+ pw.println("BubbleData state:");
+ pw.print(" selected: ");
pw.println(mSelectedBubble != null
? mSelectedBubble.getKey()
: "null");
- pw.print("expanded: ");
+ pw.print(" expanded: ");
pw.println(mExpanded);
- pw.print("stack bubble count: ");
+ pw.print("Stack bubble count: ");
pw.println(mBubbles.size());
for (Bubble bubble : mBubbles) {
bubble.dump(pw);
}
- pw.print("overflow bubble count: ");
+ pw.print("Overflow bubble count: ");
pw.println(mOverflowBubbles.size());
for (Bubble bubble : mOverflowBubbles) {
bubble.dump(pw);
}
- pw.print("summaryKeys: ");
+ pw.print("SummaryKeys: ");
pw.println(mSuppressedGroupKeys.size());
for (String key : mSuppressedGroupKeys.keySet()) {
- pw.println(" suppressing: " + key);
+ pw.println(" suppressing: " + key);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 76662c4..f56b171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -44,7 +44,7 @@
static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
static final boolean DEBUG_EXPERIMENTS = true;
static final boolean DEBUG_OVERFLOW = false;
- static final boolean DEBUG_USER_EDUCATION = false;
+ public static final boolean DEBUG_USER_EDUCATION = false;
static final boolean DEBUG_POSITIONER = false;
public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
@@ -77,20 +77,25 @@
static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) {
StringBuilder sb = new StringBuilder();
- for (Bubble bubble : bubbles) {
+ for (int i = 0; i < bubbles.size(); i++) {
+ Bubble bubble = bubbles.get(i);
if (bubble == null) {
- sb.append(" <null> !!!!!\n");
+ sb.append(" <null> !!!!!");
} else {
boolean isSelected = (selected != null
- && selected.getKey() != BubbleOverflow.KEY
+ && !BubbleOverflow.KEY.equals(selected.getKey())
&& bubble == selected);
String arrow = isSelected ? "=>" : " ";
- sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}\n",
+
+ sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}",
arrow,
bubble.getLastActivity(),
(bubble.showInShade() ? 1 : 0),
bubble.getKey()));
}
+ if (i != bubbles.size() - 1) {
+ sb.append("\n");
+ }
}
return sb.toString();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index e698601..a3eb429 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -57,6 +57,7 @@
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
@@ -411,6 +412,23 @@
setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
}
+
+ /** Updates the width of the task view if it changed. */
+ void updateTaskViewContentWidth() {
+ if (mTaskView != null) {
+ int width = getContentWidth();
+ if (mTaskView.getWidth() != width) {
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(width, MATCH_PARENT);
+ mTaskView.setLayoutParams(lp);
+ }
+ }
+ }
+
+ private int getContentWidth() {
+ boolean isStackOnLeft = mPositioner.isStackOnLeft(mStackView.getStackPosition());
+ return mPositioner.getTaskViewContentWidth(isStackOnLeft);
+ }
+
/**
* Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
* to be called after view inflate.
@@ -437,7 +455,12 @@
mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
mTaskView = new TaskView(mContext, mTaskViewTaskController);
mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
- mExpandedViewContainer.addView(mTaskView);
+
+ // set a fixed width so it is not recalculated as part of a rotation. the width will be
+ // updated manually after the rotation.
+ FrameLayout.LayoutParams lp =
+ new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT);
+ mExpandedViewContainer.addView(mTaskView, lp);
bringChildToFront(mTaskView);
}
}
@@ -470,6 +493,17 @@
R.layout.bubble_manage_button, this /* parent */, false /* attach */);
addView(mManageButton);
mManageButton.setVisibility(visibility);
+ post(() -> {
+ int touchAreaHeight =
+ getResources().getDimensionPixelSize(
+ R.dimen.bubble_manage_button_touch_area_height);
+ Rect r = new Rect();
+ mManageButton.getHitRect(r);
+ int extraTouchArea = (touchAreaHeight - r.height()) / 2;
+ r.top -= extraTouchArea;
+ r.bottom += extraTouchArea;
+ setTouchDelegate(new TouchDelegate(r, mManageButton));
+ });
}
}
@@ -949,7 +983,13 @@
if (mTaskView != null
&& mTaskView.getVisibility() == VISIBLE
&& mTaskView.isAttachedToWindow()) {
- mTaskView.onLocationChanged();
+ // post this to the looper, because if the device orientation just changed, we need to
+ // let the current shell transition complete before updating the task view bounds.
+ post(() -> {
+ if (mTaskView != null) {
+ mTaskView.onLocationChanged();
+ }
+ });
}
if (mIsOverflow) {
// post this to the looper so that the view has a chance to be laid out before it can
@@ -1094,9 +1134,9 @@
/**
* Description of current expanded view state.
*/
- public void dump(@NonNull PrintWriter pw) {
- pw.print("BubbleExpandedView");
- pw.print(" taskId: "); pw.println(mTaskId);
- pw.print(" stackView: "); pw.println(mStackView);
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.print(prefix); pw.println("BubbleExpandedView:");
+ pw.print(prefix); pw.print(" taskId: "); pw.println(mTaskId);
+ pw.print(prefix); pw.print(" stackView: "); pw.println(mStackView);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index df19757..22e836a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -27,6 +27,7 @@
import android.graphics.drawable.InsetDrawable
import android.util.PathParser
import android.view.LayoutInflater
+import android.view.View.VISIBLE
import android.widget.FrameLayout
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
@@ -112,7 +113,7 @@
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(R.color.important_conversation),
+ res.getColor(com.android.launcher3.icons.R.color.important_conversation),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
@@ -156,7 +157,9 @@
fun setShowDot(show: Boolean) {
showDot = show
- overflowBtn?.updateDotVisibility(true /* animate */)
+ if (overflowBtn?.visibility == VISIBLE) {
+ overflowBtn?.updateDotVisibility(true /* animate */)
+ }
}
/** Creates the expanded view for bubbles showing in the stack view. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index ea7053d..662a5c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,10 +16,7 @@
package com.android.wm.shell.bubbles;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Point;
@@ -28,9 +25,7 @@
import android.graphics.RectF;
import android.util.Log;
import android.view.Surface;
-import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.WindowMetrics;
import androidx.annotation.VisibleForTesting;
@@ -46,6 +41,12 @@
? "BubblePositioner"
: BubbleDebugConfig.TAG_BUBBLES;
+ /** The screen edge the bubble stack is pinned to */
+ public enum StackPinnedEdge {
+ LEFT,
+ RIGHT
+ }
+
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
/** Indicates a bubble's height should be the maximum available space. **/
@@ -54,10 +55,6 @@
public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f;
/** The max percent of screen width to use for the flyout on phone. */
public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
- /** The percent of screen width for the expanded view on a large screen. **/
- private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f;
- /** The percent of screen width for the expanded view on a large screen. **/
- private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f;
/** The percent of screen width for the expanded view on a small tablet. **/
private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
/** The percent of screen width for the expanded view when shown in the bubble bar. **/
@@ -66,15 +63,12 @@
private static final float EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT = 0.4f;
private Context mContext;
- private WindowManager mWindowManager;
+ private DeviceConfig mDeviceConfig;
private Rect mScreenRect;
private @Surface.Rotation int mRotation = Surface.ROTATION_0;
private Insets mInsets;
private boolean mImeVisible;
private int mImeHeight;
- private boolean mIsLargeScreen;
- private boolean mIsSmallTablet;
-
private Rect mPositionRect;
private int mDefaultMaxBubbles;
private int mMaxBubbles;
@@ -95,6 +89,7 @@
private int mPointerWidth;
private int mPointerHeight;
private int mPointerOverlap;
+ private int mManageButtonHeightIncludingMargins;
private int mManageButtonHeight;
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
@@ -107,44 +102,27 @@
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
- mWindowManager = windowManager;
- update();
+ mDeviceConfig = DeviceConfig.create(context, windowManager);
+ update(mDeviceConfig);
}
/**
* Available space and inset information. Call this when config changes
* occur or when added to a window.
*/
- public void update() {
- WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- if (windowMetrics == null) {
- return;
- }
- WindowInsets metricInsets = windowMetrics.getWindowInsets();
- Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
- | WindowInsets.Type.statusBars()
- | WindowInsets.Type.displayCutout());
-
- final Rect bounds = windowMetrics.getBounds();
- Configuration config = mContext.getResources().getConfiguration();
- mIsLargeScreen = config.smallestScreenWidthDp >= 600;
- if (mIsLargeScreen) {
- float largestEdgeDp = Math.max(config.screenWidthDp, config.screenHeightDp);
- mIsSmallTablet = largestEdgeDp < 960;
- } else {
- mIsSmallTablet = false;
- }
+ public void update(DeviceConfig deviceConfig) {
+ mDeviceConfig = deviceConfig;
if (BubbleDebugConfig.DEBUG_POSITIONER) {
Log.w(TAG, "update positioner:"
+ " rotation: " + mRotation
- + " insets: " + insets
- + " isLargeScreen: " + mIsLargeScreen
- + " isSmallTablet: " + mIsSmallTablet
+ + " insets: " + deviceConfig.getInsets()
+ + " isLargeScreen: " + deviceConfig.isLargeScreen()
+ + " isSmallTablet: " + deviceConfig.isSmallTablet()
+ " showingInBubbleBar: " + mShowingInBubbleBar
- + " bounds: " + bounds);
+ + " bounds: " + deviceConfig.getWindowBounds());
}
- updateInternal(mRotation, insets, bounds);
+ updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
@VisibleForTesting
@@ -172,25 +150,24 @@
mExpandedViewLargeScreenWidth = isLandscape()
? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT)
: (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT);
- } else if (mIsSmallTablet) {
+ } else if (mDeviceConfig.isSmallTablet()) {
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
} else {
- mExpandedViewLargeScreenWidth = isLandscape()
- ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT)
- : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT);
+ mExpandedViewLargeScreenWidth =
+ res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width);
}
- if (mIsLargeScreen) {
- if (isLandscape() && !mIsSmallTablet) {
+ if (mDeviceConfig.isLargeScreen()) {
+ if (mDeviceConfig.isSmallTablet()) {
+ final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
+ mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
+ mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
+ } else {
mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize(
R.dimen.bubble_expanded_view_largescreen_landscape_padding);
mExpandedViewLargeScreenInsetFurthestEdge = bounds.width()
- mExpandedViewLargeScreenInsetClosestEdge
- mExpandedViewLargeScreenWidth;
- } else {
- final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
- mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
- mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
}
} else {
mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding;
@@ -202,7 +179,10 @@
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
- mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+ mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+ mManageButtonHeightIncludingMargins =
+ mManageButtonHeight
+ + 2 * res.getDimensionPixelSize(R.dimen.bubble_manage_button_margin);
mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mMinimumFlyoutWidthLargeScreen = res.getDimensionPixelSize(
@@ -259,13 +239,24 @@
/** @return whether the device is in landscape orientation. */
public boolean isLandscape() {
- return mContext.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ return mDeviceConfig.isLandscape();
+ }
+
+ /**
+ * On large screen (not small tablet), while in portrait, expanded bubbles are aligned to
+ * the bottom of the screen.
+ *
+ * @return whether bubbles are bottom aligned while expanded
+ */
+ public boolean areBubblesBottomAligned() {
+ return isLargeScreen()
+ && !mDeviceConfig.isSmallTablet()
+ && !isLandscape();
}
/** @return whether the screen is considered large. */
public boolean isLargeScreen() {
- return mIsLargeScreen;
+ return mDeviceConfig.isLargeScreen();
}
/**
@@ -276,7 +267,7 @@
* to the left or right side.
*/
public boolean showBubblesVertically() {
- return isLandscape() || mIsLargeScreen;
+ return isLandscape() || mDeviceConfig.isLargeScreen();
}
/** Size of the bubble. */
@@ -329,7 +320,7 @@
}
private int getExpandedViewLargeScreenInsetFurthestEdge(boolean isOverflow) {
- if (isOverflow && mIsLargeScreen) {
+ if (isOverflow && mDeviceConfig.isLargeScreen()) {
return mScreenRect.width()
- mExpandedViewLargeScreenInsetClosestEdge
- mOverflowWidth;
@@ -353,7 +344,7 @@
final int pointerTotalHeight = getPointerSize();
final int expandedViewLargeScreenInsetFurthestEdge =
getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
- if (mIsLargeScreen) {
+ if (mDeviceConfig.isLargeScreen()) {
// Note:
// If we're in portrait OR if we're a small tablet, then the two insets values will
// be equal. If we're landscape and a large tablet, the two values will be different.
@@ -396,6 +387,13 @@
}
}
+ /** Returns the width of the task view content. */
+ public int getTaskViewContentWidth(boolean onLeft) {
+ int[] paddings = getExpandedViewContainerPadding(onLeft, /* isOverflow = */ false);
+ int pointerOffset = showBubblesVertically() ? getPointerSize() : 0;
+ return mPositionRect.width() - paddings[0] - paddings[2] - pointerOffset;
+ }
+
/** Gets the y position of the expanded view if it was top-aligned. */
public float getExpandedViewYTopAligned() {
final int top = getAvailableRect().top;
@@ -411,6 +409,9 @@
* the screen and the size of the elements around it (e.g. padding, pointer, manage button).
*/
public int getMaxExpandedViewHeight(boolean isOverflow) {
+ if (mDeviceConfig.isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isOverflow) {
+ return getExpandedViewHeightForLargeScreen();
+ }
// Subtract top insets because availableRect.height would account for that
int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
int paddingTop = showBubblesVertically()
@@ -420,7 +421,7 @@
int pointerSize = showBubblesVertically()
? mPointerWidth
: (mPointerHeight + mPointerMargin);
- int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
return getAvailableRect().height()
- expandedContainerY
- paddingTop
@@ -429,12 +430,25 @@
}
/**
+ * Returns the height to use for the expanded view when showing on a large screen.
+ */
+ public int getExpandedViewHeightForLargeScreen() {
+ // the expanded view height on large tablets is calculated based on the shortest screen
+ // size and is the same in both portrait and landscape
+ int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
+ int shortestScreenSide = Math.min(getScreenRect().height(), getScreenRect().width());
+ // Subtract pointer size because it's laid out in LinearLayout with the expanded view.
+ return shortestScreenSide - maxVerticalInset * 2
+ - mManageButtonHeight - mPointerWidth - mExpandedViewPadding * 2;
+ }
+
+ /**
* Determines the height for the bubble, ensuring a minimum height. If the height should be as
* big as available, returns {@link #MAX_HEIGHT}.
*/
public float getExpandedViewHeight(BubbleViewProvider bubble) {
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
- if (isOverflow && showBubblesVertically() && !mIsLargeScreen) {
+ if (isOverflow && showBubblesVertically() && !mDeviceConfig.isLargeScreen()) {
// overflow in landscape on phone is max
return MAX_HEIGHT;
}
@@ -461,12 +475,21 @@
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float expandedViewHeight = getExpandedViewHeight(bubble);
float topAlignment = getExpandedViewYTopAligned();
+ int manageButtonHeight =
+ isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
+
+ // On largescreen portrait bubbles are bottom aligned.
+ if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
+ return mPositionRect.bottom - manageButtonHeight
+ - getExpandedViewHeightForLargeScreen() - mPointerWidth;
+ }
+
if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) {
// Top-align when bubbles are shown at the top or are max size.
return topAlignment;
}
+
// If we're here, we're showing vertically & developer has made height less than maximum.
- int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
float pointerPosition = getPointerPosition(bubblePosition);
float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
float topIfCentered = pointerPosition - (expandedViewHeight / 2);
@@ -514,11 +537,9 @@
*/
public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
boolean showBubblesVertically = showBubblesVertically();
- boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
- == LAYOUT_DIRECTION_RTL;
int onScreenIndex;
- if (showBubblesVertically || !isRtl) {
+ if (showBubblesVertically || !mDeviceConfig.isRtl()) {
onScreenIndex = index;
} else {
// If bubbles are shown horizontally, check if RTL language is used.
@@ -526,23 +547,17 @@
// Last bubble has screen index 0 and first bubble has max screen index value.
onScreenIndex = state.numberOfBubbles - 1 - index;
}
-
final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles);
- final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
- final float centerPosition = showBubblesVertically
- ? mPositionRect.centerY()
- : mPositionRect.centerX();
- // alignment - centered on the edge
- final float rowStart = centerPosition - (expandedStackSize / 2f);
+ final float rowStart = getBubbleRowStart(state);
float x;
float y;
if (showBubblesVertically) {
int inset = mExpandedViewLargeScreenInsetClosestEdge;
y = rowStart + positionInRow;
- int left = mIsLargeScreen
+ int left = mDeviceConfig.isLargeScreen()
? inset - mExpandedViewPadding - mBubbleSize
: mPositionRect.left;
- int right = mIsLargeScreen
+ int right = mDeviceConfig.isLargeScreen()
? mPositionRect.right - inset + mExpandedViewPadding
: mPositionRect.right - mBubbleSize;
x = state.onLeft
@@ -559,6 +574,25 @@
return new PointF(x, y);
}
+ private float getBubbleRowStart(BubbleStackView.StackViewState state) {
+ final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
+ final float rowStart;
+ if (areBubblesBottomAligned()) {
+ final float expandedViewHeight = getExpandedViewHeightForLargeScreen();
+ final float expandedViewBottom = mScreenRect.bottom
+ - Math.max(mInsets.bottom, mInsets.top)
+ - mManageButtonHeight - mPointerWidth;
+ final float expandedViewCenter = expandedViewBottom - (expandedViewHeight / 2f);
+ rowStart = expandedViewCenter - (expandedStackSize / 2f);
+ } else {
+ final float centerPosition = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ rowStart = centerPosition - (expandedStackSize / 2f);
+ }
+ return rowStart;
+ }
+
/**
* Returns the position of the bubble on-screen when the stack is expanded and the IME
* is showing.
@@ -579,9 +613,8 @@
final float bottomHeight = getImeHeight() + mInsets.bottom + (mSpacingBetweenBubbles * 2);
final float bottomInset = mScreenRect.bottom - bottomHeight;
final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
- final float centerPosition = mPositionRect.centerY();
- final float rowBottom = centerPosition + (expandedStackSize / 2f);
- final float rowTop = centerPosition - (expandedStackSize / 2f);
+ final float rowTop = getBubbleRowStart(state);
+ final float rowBottom = rowTop + expandedStackSize;
float rowTopForIme = rowTop;
if (rowBottom > bottomInset) {
// We overlap with IME, must shift the bubbles
@@ -678,13 +711,19 @@
* @param isAppBubble whether this start position is for an app bubble or not.
*/
public PointF getDefaultStartPosition(boolean isAppBubble) {
- final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection();
// Normal bubbles start on the left if we're in LTR, right otherwise.
// TODO (b/294284894): update language around "app bubble" here
// App bubbles start on the right in RTL, left otherwise.
- final boolean startOnLeft = isAppBubble
- ? layoutDirection == LAYOUT_DIRECTION_RTL
- : layoutDirection != LAYOUT_DIRECTION_RTL;
+ final boolean startOnLeft = isAppBubble ? mDeviceConfig.isRtl() : !mDeviceConfig.isRtl();
+ return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT);
+ }
+
+ /**
+ * The stack position to use if user education is being shown.
+ *
+ * @param stackPinnedEdge the screen edge the stack is pinned to.
+ */
+ public PointF getStartPosition(StackPinnedEdge stackPinnedEdge) {
final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
1 /* default starts with 1 bubble */);
if (isLargeScreen()) {
@@ -693,7 +732,7 @@
final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
final float offset = desiredY / mScreenRect.height();
return new BubbleStackView.RelativeStackPosition(
- startOnLeft,
+ stackPinnedEdge == StackPinnedEdge.LEFT,
offset)
.getAbsolutePositionInRegion(allowableStackPositionRegion);
} else {
@@ -701,7 +740,7 @@
R.dimen.bubble_stack_starting_offset_y);
// TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
return new BubbleStackView.RelativeStackPosition(
- startOnLeft,
+ stackPinnedEdge == StackPinnedEdge.LEFT,
startingVerticalOffset / mPositionRect.height())
.getAbsolutePositionInRegion(allowableStackPositionRegion);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 52c9bf8..b7f749e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -25,6 +25,9 @@
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT;
+import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT;
+import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
@@ -56,7 +59,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
+import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
+import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -131,8 +136,6 @@
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
- private static final float SCRIM_ALPHA = 0.32f;
-
/** Minimum alpha value for scrim when alpha is being changed via drag */
private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f;
@@ -213,7 +216,8 @@
private ExpandedViewAnimationController mExpandedViewAnimationController;
private View mScrim;
- private boolean mScrimAnimating;
+ @Nullable
+ private ViewPropertyAnimator mScrimAnimation;
private View mManageMenuScrim;
private FrameLayout mExpandedViewContainer;
@@ -305,8 +309,7 @@
String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
getBubblesOnScreen(), getExpandedBubble());
- pw.print(" stack visibility : "); pw.println(getVisibility());
- pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen);
+ pw.println(" bubbles on screen: "); pw.println(bubblesOnScreen);
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing());
pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
@@ -314,7 +317,8 @@
pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha());
pw.print(" expandedContainerMatrix: ");
pw.println(mExpandedViewContainer.getAnimationMatrix());
-
+ pw.print(" stack visibility : "); pw.println(getVisibility());
+ pw.print(" temporarilyInvisible: "); pw.println(mTemporarilyInvisible);
mStackAnimationController.dump(pw);
mExpandedAnimationController.dump(pw);
@@ -533,8 +537,8 @@
return;
}
- final boolean clickedBubbleIsCurrentlyExpandedBubble =
- clickedBubble.getKey().equals(mExpandedBubble.getKey());
+ final boolean clickedBubbleIsCurrentlyExpandedBubble = mExpandedBubble != null
+ && clickedBubble.getKey().equals(mExpandedBubble.getKey());
if (isExpanded()) {
mExpandedAnimationController.onGestureFinished();
@@ -749,8 +753,8 @@
float collapsed = -Math.min(dy, 0);
mExpandedViewAnimationController.updateDrag((int) collapsed);
- // Update scrim
- if (!mScrimAnimating) {
+ // Update scrim if it's not animating already
+ if (mScrimAnimation == null) {
mScrim.setAlpha(getScrimAlphaForDrag(collapsed));
}
}
@@ -769,8 +773,8 @@
} else {
mExpandedViewAnimationController.animateBackToExpanded();
- // Update scrim
- if (!mScrimAnimating) {
+ // Update scrim if it's not animating already
+ if (mScrimAnimation == null) {
showScrim(true, null /* runnable */);
}
}
@@ -779,14 +783,15 @@
private float getScrimAlphaForDrag(float dragAmount) {
// dragAmount should be negative as we allow scroll up only
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
+ float alphaRange = BUBBLE_EXPANDED_SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
int dragMax = mExpandedBubble.getExpandedView().getContentHeight();
float dragFraction = dragAmount / dragMax;
- return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG);
+ return Math.max(BUBBLE_EXPANDED_SCRIM_ALPHA - alphaRange * dragFraction,
+ MIN_SCRIM_ALPHA_FOR_DRAG);
}
- return SCRIM_ALPHA;
+ return BUBBLE_EXPANDED_SCRIM_ALPHA;
}
};
@@ -997,7 +1002,8 @@
mOrientationChangedListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- mPositioner.update();
+ mPositioner.update(DeviceConfig.create(mContext, mContext.getSystemService(
+ WindowManager.class)));
onDisplaySizeChanged();
mExpandedAnimationController.updateResources();
mStackAnimationController.updateResources();
@@ -1010,15 +1016,21 @@
}
if (mIsExpanded) {
- // Re-draw bubble row and pointer for new orientation.
- beforeExpandedViewAnimation();
+ // update the expanded view and pointer location for the new orientation.
+ hideFlyoutImmediate();
+ mExpandedViewContainer.setAlpha(0f);
+ updateExpandedView();
updateOverflowVisibility();
- updatePointerPosition(false /* forIme */);
- mExpandedAnimationController.expandFromStack(() -> {
- afterExpandedViewAnimation();
- mExpandedViewContainer.setVisibility(VISIBLE);
- showManageMenu(mShowingManage);
- } /* after */);
+ updatePointerPosition(false);
+ requestUpdate();
+ if (mShowingManage) {
+ // if we're showing the menu after rotation, post it to the looper
+ // to make sure that the location of the menu button is correct
+ post(() -> showManageMenu(true));
+ } else {
+ showManageMenu(false);
+ }
+
PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
getState());
final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
@@ -1027,6 +1039,7 @@
mExpandedViewContainer.setTranslationY(translationY);
mExpandedViewContainer.setAlpha(1f);
}
+
removeOnLayoutChangeListener(mOrientationChangedListener);
};
final float maxDismissSize = getResources().getDimensionPixelSize(
@@ -1293,6 +1306,9 @@
return shouldShow;
}
+ /**
+ * Show manage education if should show and was not showing before.
+ */
private void maybeShowManageEdu() {
if (!shouldShowManageEdu()) {
return;
@@ -1301,7 +1317,16 @@
mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
}
- mManageEduView.show(mExpandedBubble.getExpandedView());
+ showManageEdu();
+ }
+
+ /**
+ * Show manage education if was not showing before.
+ */
+ private void showManageEdu() {
+ if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) return;
+ mManageEduView.show(mExpandedBubble.getExpandedView(),
+ mStackAnimationController.isStackOnLeftSide());
}
@VisibleForTesting
@@ -1347,10 +1372,21 @@
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
}
+ return showStackEdu();
+ }
+
+ /**
+ * @return true if education view for the collapsed stack was not showing before.
+ */
+ private boolean showStackEdu() {
+ // Stack appears on top of the education views
mBubbleContainer.bringToFront();
// Ensure the stack is in the correct spot
- mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
- return mStackEduView.show(mPositioner.getDefaultStartPosition());
+ PointF position = mPositioner.getStartPosition(
+ mStackAnimationController.isStackOnLeftSide() ? LEFT : RIGHT);
+ // Animate stack to the position
+ mStackAnimationController.springStackAfterFling(position.x, position.y);
+ return mStackEduView.show(position);
}
@VisibleForTesting
@@ -1364,16 +1400,13 @@
removeView(mStackEduView);
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
- mBubbleContainer.bringToFront(); // Stack appears on top of the stack education
- // Ensure the stack is in the correct spot
- mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
- mStackEduView.show(mPositioner.getDefaultStartPosition());
+ showStackEdu();
}
if (isManageEduVisible()) {
removeView(mManageEduView);
mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
- mManageEduView.show(mExpandedBubble.getExpandedView());
+ showManageEdu();
}
}
@@ -1483,6 +1516,11 @@
updateExpandedView();
}
setUpManageMenu();
+ if (mShowingManage) {
+ // the manage menu location depends on the manage button location which may need a
+ // layout pass, so post this to the looper
+ post(() -> showManageMenu(true));
+ }
}
@Override
@@ -1497,7 +1535,8 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mPositioner.update();
+ WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager)));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
}
@@ -1784,13 +1823,14 @@
mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
mStackAnimationController.setStackPosition(startPosition);
mExpandedAnimationController.setCollapsePoint(startPosition);
- // Set the translation x so that this bubble will animate in from the same side they
- // expand / collapse on.
- bubble.getIconView().setTranslationX(startPosition.x);
} else if (firstBubble) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
+ // Set the view translation x so that this bubble will animate in from the same side they
+ // expand / collapse on.
+ bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x);
+
mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
@@ -1825,9 +1865,12 @@
}
bubble.cleanupViews(); // cleans up the icon view
updateExpandedView(); // resets state for no expanded bubble
+ mExpandedBubble = null;
});
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
+ } else if (getBubbleCount() == 1) {
+ mExpandedBubble = null;
}
// Remove it from the views
for (int i = 0; i < getBubbleCount(); i++) {
@@ -1864,6 +1907,14 @@
: GONE);
}
+ private void updateOverflowDotVisibility(boolean expanding) {
+ if (mBubbleOverflow.showDot()) {
+ mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> {
+ mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE);
+ });
+ }
+ }
+
// via BubbleData.Listener
void updateBubble(Bubble bubble) {
animateInFlyoutForBubble(bubble);
@@ -2037,6 +2088,7 @@
});
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
+ announceExpandForAccessibility(mExpandedBubble, mIsExpanded);
}
/**
@@ -2053,6 +2105,34 @@
}
}
+ private void announceExpandForAccessibility(BubbleViewProvider bubble, boolean expanded) {
+ if (bubble instanceof Bubble) {
+ String contentDescription = getBubbleContentDescription((Bubble) bubble);
+ String message = getResources().getString(
+ expanded
+ ? R.string.bubble_accessibility_announce_expand
+ : R.string.bubble_accessibility_announce_collapse, contentDescription);
+ announceForAccessibility(message);
+ }
+ }
+
+ @NonNull
+ private String getBubbleContentDescription(Bubble bubble) {
+ final String appName = bubble.getAppName();
+ final String title = bubble.getTitle() != null
+ ? bubble.getTitle()
+ : getResources().getString(R.string.notification_bubble_title);
+
+ if (appName == null || title.equals(appName)) {
+ // App bubble title equals the app name, so return only the title to avoid having
+ // content description like: `<app> from <app>`.
+ return title;
+ } else {
+ return getResources().getString(
+ R.string.bubble_content_description_single, title, appName);
+ }
+ }
+
private boolean isGestureNavEnabled() {
return mContext.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
@@ -2199,26 +2279,27 @@
private void showScrim(boolean show, Runnable after) {
AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animation) {
- mScrimAnimating = true;
- }
-
- @Override
public void onAnimationEnd(Animator animation) {
- mScrimAnimating = false;
+ mScrimAnimation = null;
if (after != null) {
after.run();
}
}
};
+ if (mScrimAnimation != null) {
+ // Cancel scrim animation if it animates
+ mScrimAnimation.cancel();
+ }
if (show) {
- mScrim.animate()
+ mScrimAnimation = mScrim.animate();
+ mScrimAnimation
.setInterpolator(ALPHA_IN)
- .alpha(SCRIM_ALPHA)
+ .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
.setListener(listener)
.start();
} else {
- mScrim.animate()
+ mScrimAnimation = mScrim.animate();
+ mScrimAnimation
.alpha(0f)
.setInterpolator(ALPHA_OUT)
.setListener(listener)
@@ -2245,6 +2326,7 @@
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
+ updateOverflowDotVisibility(true /* expanding */);
} /* after */);
int index;
if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
@@ -2376,11 +2458,15 @@
// since we're about to animate collapsed.
mExpandedAnimationController.notifyPreparingToCollapse();
- final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController
- .getStackPositionAlongNearestHorizontalEdge()
- /* collapseTo */,
- () -> mBubbleContainer.setActiveController(mStackAnimationController));
+ updateOverflowDotVisibility(false /* expanding */);
+ final Runnable collapseBackToStack = () ->
+ mExpandedAnimationController.collapseBackToStack(
+ mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
+ /* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded,
+ () -> {
+ mBubbleContainer.setActiveController(mStackAnimationController);
+ updateOverflowVisibility();
+ });
final Runnable after = () -> {
final BubbleViewProvider previouslySelected = mExpandedBubble;
@@ -2395,7 +2481,6 @@
Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
mExpandedBubble));
}
- updateOverflowVisibility();
updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
@@ -2943,7 +3028,7 @@
mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
- .alpha(show ? SCRIM_ALPHA : 0f)
+ .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
.withEndAction(endAction)
.start();
@@ -3215,6 +3300,7 @@
mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
mPositioner.showBubblesVertically() ? p.y : p.x));
mExpandedViewContainer.setTranslationX(0f);
+ mExpandedBubble.getExpandedView().updateTaskViewContentWidth();
mExpandedBubble.getExpandedView().updateView(
mExpandedViewContainer.getLocationOnScreen());
updatePointerPosition(false /* forIme */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 39e3180..bb30c5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -155,14 +155,14 @@
Bitmap rawBadgeBitmap;
// Only populated when showing in taskbar
- BubbleBarExpandedView bubbleBarExpandedView;
+ @Nullable BubbleBarExpandedView bubbleBarExpandedView;
// These are only populated when not showing in taskbar
- BadgedImageView imageView;
- BubbleExpandedView expandedView;
+ @Nullable BadgedImageView imageView;
+ @Nullable BubbleExpandedView expandedView;
int dotColor;
Path dotPath;
- Bubble.FlyoutMessage flyoutMessage;
+ @Nullable Bubble.FlyoutMessage flyoutMessage;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
new file mode 100644
index 0000000..9e8a385
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * Observer used to identify tasks that are opening or moving to front. If a bubble activity is
+ * currently opened when this happens, we'll collapse the bubbles.
+ */
+public class BubblesTransitionObserver implements Transitions.TransitionObserver {
+
+ private BubbleController mBubbleController;
+ private BubbleData mBubbleData;
+
+ public BubblesTransitionObserver(BubbleController controller,
+ BubbleData bubbleData) {
+ mBubbleController = controller;
+ mBubbleData = bubbleData;
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // We only care about opens / move to fronts when bubbles are expanded & not animating.
+ if (taskInfo == null
+ || taskInfo.taskId == INVALID_TASK_ID
+ || !TransitionUtil.isOpeningType(change.getMode())
+ || mBubbleController.isStackAnimating()
+ || !mBubbleData.isExpanded()
+ || mBubbleData.getSelectedBubble() == null) {
+ continue;
+ }
+ int expandedId = mBubbleData.getSelectedBubble().getTaskId();
+ // If the task id that's opening is the same as the expanded bubble, skip collapsing
+ // because it is our bubble that is opening.
+ if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+ mBubbleData.setExpanded(false);
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {
+
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+
+ }
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
+
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt
new file mode 100644
index 0000000..9293309
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.bubbles
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.graphics.Insets
+import android.graphics.Rect
+import android.view.View.LAYOUT_DIRECTION_RTL
+import android.view.WindowInsets
+import android.view.WindowManager
+import kotlin.math.max
+
+/** Contains device configuration used for positioning bubbles on the screen. */
+data class DeviceConfig(
+ val isLargeScreen: Boolean,
+ val isSmallTablet: Boolean,
+ val isLandscape: Boolean,
+ val isRtl: Boolean,
+ val windowBounds: Rect,
+ val insets: Insets
+) {
+ companion object {
+
+ private const val LARGE_SCREEN_MIN_EDGE_DP = 600
+ private const val SMALL_TABLET_MAX_EDGE_DP = 960
+
+ @JvmStatic
+ fun create(context: Context, windowManager: WindowManager): DeviceConfig {
+ val windowMetrics = windowManager.currentWindowMetrics
+ val metricInsets = windowMetrics.windowInsets
+ val insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
+ or WindowInsets.Type.statusBars()
+ or WindowInsets.Type.displayCutout())
+ val windowBounds = windowMetrics.bounds
+ val config: Configuration = context.resources.configuration
+ val isLargeScreen = config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP
+ val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp)
+ val isSmallTablet = isLargeScreen && largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP
+ val isLandscape = context.resources.configuration.orientation == ORIENTATION_LANDSCAPE
+ val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
+ return DeviceConfig(
+ isLargeScreen = isLargeScreen,
+ isSmallTablet = isSmallTablet,
+ isLandscape = isLandscape,
+ isRtl = isRtl,
+ windowBounds = windowBounds,
+ insets = insets
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
index ed36240..48692d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
@@ -22,6 +22,7 @@
fun DismissView.setup() {
setup(DismissView.Config(
+ dismissViewResId = R.id.dismiss_view,
targetSizeResId = R.dimen.dismiss_circle_size,
iconSizeResId = R.dimen.dismiss_target_x_size,
bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 4dda068..5776ad1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -39,4 +39,6 @@
oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
+ oneway void showUserEducation(in int positionX, in int positionY) = 8;
+
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 1b41f79..61e17c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -19,6 +19,7 @@
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -32,10 +33,10 @@
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView constructor(context: Context, positioner: BubblePositioner)
- : LinearLayout(context) {
+class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
+ private val TAG =
+ if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION: Long = 200
@@ -62,7 +63,7 @@
override fun setLayoutDirection(layoutDirection: Int) {
super.setLayoutDirection(layoutDirection)
- setDrawableDirection()
+ setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
}
override fun onFinishInflate() {
@@ -71,8 +72,10 @@
}
private fun setButtonColor() {
- val typedArray = mContext.obtainStyledAttributes(intArrayOf(
- com.android.internal.R.attr.colorAccentPrimary))
+ val typedArray =
+ mContext.obtainStyledAttributes(
+ intArrayOf(com.android.internal.R.attr.colorAccentPrimary)
+ )
val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT)
typedArray.recycle()
@@ -81,11 +84,11 @@
gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor))
}
- private fun setDrawableDirection() {
+ private fun setDrawableDirection(isOnLeft: Boolean) {
manageView.setBackgroundResource(
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
- R.drawable.bubble_stack_user_education_bg_rtl
- else R.drawable.bubble_stack_user_education_bg)
+ if (isOnLeft) R.drawable.bubble_stack_user_education_bg
+ else R.drawable.bubble_stack_user_education_bg_rtl
+ )
}
/**
@@ -93,48 +96,31 @@
* bubble stack is expanded for the first time.
*
* @param expandedView the expandedView the user education is shown on top of.
+ * @param isStackOnLeft the bubble stack position on the screen
*/
- fun show(expandedView: BubbleExpandedView) {
+ fun show(expandedView: BubbleExpandedView, isStackOnLeft: Boolean) {
setButtonColor()
if (visibility == VISIBLE) return
bubbleExpandedView = expandedView
expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
- layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
- context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
- else ViewGroup.LayoutParams.MATCH_PARENT
-
alpha = 0f
visibility = View.VISIBLE
expandedView.getManageButtonBoundsOnScreen(realManageButtonRect)
- val isRTL = mContext.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
- if (isRTL) {
- val rightPadding = positioner.screenRect.right - realManageButtonRect.right -
- expandedView.manageButtonMargin
- manageView.setPadding(manageView.paddingLeft, manageView.paddingTop,
- rightPadding, manageView.paddingBottom)
- } else {
- manageView.setPadding(realManageButtonRect.left - expandedView.manageButtonMargin,
- manageView.paddingTop, manageView.paddingRight, manageView.paddingBottom)
- }
+ layoutManageView(realManageButtonRect, expandedView.manageButtonMargin, isStackOnLeft)
+
post {
- manageButton
- .setOnClickListener {
- hide()
- expandedView.requireViewById<View>(R.id.manage_button).performClick()
- }
+ manageButton.setOnClickListener {
+ hide()
+ expandedView.requireViewById<View>(R.id.manage_button).performClick()
+ }
gotItButton.setOnClickListener { hide() }
setOnClickListener { hide() }
val offsetViewBounds = Rect()
manageButton.getDrawingRect(offsetViewBounds)
manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds)
- if (isRTL && (positioner.isLargeScreen || positioner.isLandscape)) {
- translationX = (positioner.screenRect.right - width).toFloat()
- } else {
- translationX = 0f
- }
translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat()
bringToFront()
animate()
@@ -145,6 +131,79 @@
setShouldShow(false)
}
+ /**
+ * On tablet the user education is aligned to the left or to right side depending on where the
+ * stack is positioned when collapsed. On phone the user education follows the layout direction.
+ *
+ * @param manageButtonRect the manage button rect on the screen
+ * @param manageButtonMargin the manage button margin
+ * @param isStackOnLeft the bubble stack position on the screen
+ */
+ private fun layoutManageView(
+ manageButtonRect: Rect,
+ manageButtonMargin: Int,
+ isStackOnLeft: Boolean
+ ) {
+ val isLTR = resources.configuration.layoutDirection == LAYOUT_DIRECTION_LTR
+ val isPinnedLeft = if (positioner.isLargeScreen) isStackOnLeft else isLTR
+ val paddingHorizontal =
+ resources.getDimensionPixelSize(R.dimen.bubble_user_education_padding_horizontal)
+
+ // The user education view background image direction
+ setDrawableDirection(isPinnedLeft)
+
+ // The user education view layout gravity
+ gravity = if (isPinnedLeft) Gravity.LEFT else Gravity.RIGHT
+
+ // The user education view width
+ manageView.layoutParams.width =
+ when {
+ // Left-to-Right direction and the education is on the right side
+ isLTR && !isPinnedLeft ->
+ positioner.screenRect.right -
+ (manageButtonRect.left - manageButtonMargin - paddingHorizontal)
+ // Right-to-Left direction and the education is on the left side
+ !isLTR && isPinnedLeft ->
+ manageButtonRect.right + manageButtonMargin + paddingHorizontal
+ // Large screen and the education position matches the layout direction
+ positioner.isLargeScreen -> ViewGroup.LayoutParams.WRAP_CONTENT
+ // Small screen, landscape orientation
+ positioner.isLandscape ->
+ resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
+ // Otherwise
+ else -> ViewGroup.LayoutParams.MATCH_PARENT
+ }
+
+ // The user education view margin on the opposite side of where it's pinned
+ (manageView.layoutParams as MarginLayoutParams).apply {
+ val edgeMargin =
+ resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
+ leftMargin = if (isPinnedLeft) 0 else edgeMargin
+ rightMargin = if (isPinnedLeft) edgeMargin else 0
+ }
+
+ // The user education view padding
+ manageView.apply {
+ val paddingLeft =
+ if (isLTR && isPinnedLeft) {
+ // Offset on the left to align with the manage button
+ manageButtonRect.left - manageButtonMargin
+ } else {
+ // Use default padding
+ paddingHorizontal
+ }
+ val paddingRight =
+ if (!isLTR && !isPinnedLeft) {
+ // Offset on the right to align with the manage button
+ positioner.screenRect.right - manageButtonRect.right - manageButtonMargin
+ } else {
+ // Use default padding
+ paddingHorizontal
+ }
+ setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
+ }
+ }
+
fun hide() {
bubbleExpandedView?.taskView?.setObscuredTouchRect(null)
if (visibility != VISIBLE || isHiding) return
@@ -160,9 +219,12 @@
}
private fun setShouldShow(shouldShow: Boolean) {
- context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
+ context
+ .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .apply()
}
}
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
\ No newline at end of file
+const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 5e3a077..2cabb65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -21,7 +21,6 @@
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
-import android.view.View.OnKeyListener
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
@@ -30,16 +29,17 @@
import com.android.wm.shell.animation.Interpolators
/**
- * User education view to highlight the collapsed stack of bubbles.
- * Shown only the first time a user taps the stack.
+ * User education view to highlight the collapsed stack of bubbles. Shown only the first time a user
+ * taps the stack.
*/
-class StackEducationView constructor(
+class StackEducationView(
context: Context,
positioner: BubblePositioner,
controller: BubbleController
) : LinearLayout(context) {
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
+ private val TAG =
+ if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION: Long = 200
@@ -69,7 +69,7 @@
override fun setLayoutDirection(layoutDirection: Int) {
super.setLayoutDirection(layoutDirection)
- setDrawableDirection()
+ setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
}
override fun onFinishInflate() {
@@ -111,16 +111,16 @@
descTextView.setTextColor(textColor)
}
- private fun setDrawableDirection() {
+ private fun setDrawableDirection(isOnLeft: Boolean) {
view.setBackgroundResource(
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR)
- R.drawable.bubble_stack_user_education_bg
- else R.drawable.bubble_stack_user_education_bg_rtl)
+ if (isOnLeft) R.drawable.bubble_stack_user_education_bg
+ else R.drawable.bubble_stack_user_education_bg_rtl
+ )
}
/**
- * If necessary, shows the user education view for the bubble stack. This appears the first
- * time a user taps on a bubble.
+ * If necessary, shows the user education view for the bubble stack. This appears the first time
+ * a user taps on a bubble.
*
* @return true if user education was shown and wasn't showing before, false otherwise.
*/
@@ -129,29 +129,44 @@
if (visibility == VISIBLE) return false
controller.updateWindowFlagsForBackpress(true /* interceptBack */)
- layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
- context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
- else ViewGroup.LayoutParams.MATCH_PARENT
+ layoutParams.width =
+ if (positioner.isLargeScreen || positioner.isLandscape)
+ context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
+ else ViewGroup.LayoutParams.MATCH_PARENT
- val stackPadding = context.resources.getDimensionPixelSize(
- R.dimen.bubble_user_education_stack_padding)
+ val isStackOnLeft = positioner.isStackOnLeft(stackPosition)
+ (view.layoutParams as MarginLayoutParams).apply {
+ // Update the horizontal margins depending on the stack position
+ val edgeMargin =
+ resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
+ leftMargin = if (isStackOnLeft) 0 else edgeMargin
+ rightMargin = if (isStackOnLeft) edgeMargin else 0
+ }
+
+ val stackPadding =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_user_education_stack_padding)
setAlpha(0f)
setVisibility(View.VISIBLE)
+ setDrawableDirection(isOnLeft = isStackOnLeft)
post {
requestFocus()
with(view) {
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
- setPadding(positioner.bubbleSize + stackPadding, paddingTop, paddingRight,
- paddingBottom)
+ if (isStackOnLeft) {
+ setPadding(
+ positioner.bubbleSize + stackPadding,
+ paddingTop,
+ paddingRight,
+ paddingBottom
+ )
+ translationX = 0f
} else {
- setPadding(paddingLeft, paddingTop, positioner.bubbleSize + stackPadding,
- paddingBottom)
- if (positioner.isLargeScreen || positioner.isLandscape) {
- translationX = (positioner.screenRect.right - width - stackPadding)
- .toFloat()
- } else {
- translationX = 0f
- }
+ setPadding(
+ paddingLeft,
+ paddingTop,
+ positioner.bubbleSize + stackPadding,
+ paddingBottom
+ )
+ translationX = (positioner.screenRect.right - width - stackPadding).toFloat()
}
translationY = stackPosition.y + positioner.bubbleSize / 2 - getHeight() / 2
}
@@ -168,7 +183,7 @@
* If necessary, hides the stack education view.
*
* @param isExpanding if true this indicates the hide is happening due to the bubble being
- * expanded, false if due to a touch outside of the bubble stack.
+ * expanded, false if due to a touch outside of the bubble stack.
*/
fun hide(isExpanding: Boolean) {
if (visibility != VISIBLE || isHiding) return
@@ -182,9 +197,12 @@
}
private fun setShouldShow(shouldShow: Boolean) {
- context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
+ context
+ .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .apply()
}
}
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
\ No newline at end of file
+const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 4d7042b..5b0239f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,6 +19,7 @@
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.res.Resources;
import android.graphics.Path;
@@ -34,6 +35,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BadgedImageView;
+import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -63,6 +66,12 @@
/** Damping ratio for expand/collapse spring. */
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
+ /**
+ * Damping ratio for the overflow bubble spring; this is less bouncy so it doesn't bounce behind
+ * the top bubble when it goes to disappear.
+ */
+ private static final float DAMPING_RATIO_OVERFLOW_BOUNCY = 0.90f;
+
/** Stiffness for the expand/collapse path-following animation. */
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400;
@@ -98,6 +107,7 @@
private Runnable mAfterExpand;
private Runnable mAfterCollapse;
private PointF mCollapsePoint;
+ private boolean mFadeBubblesDuringCollapse = false;
/**
* Whether the dragged out bubble is springing towards the touch point, rather than using the
@@ -192,12 +202,14 @@
}
/** Animate collapsing the bubbles back to their stacked position. */
- public void collapseBackToStack(PointF collapsePoint, Runnable after) {
+ public void collapseBackToStack(PointF collapsePoint, boolean fadeBubblesDuringCollapse,
+ Runnable after) {
mAnimatingExpand = false;
mPreparingToCollapse = false;
mAnimatingCollapse = true;
mAfterCollapse = after;
mCollapsePoint = collapsePoint;
+ mFadeBubblesDuringCollapse = fadeBubblesDuringCollapse;
startOrUpdatePathAnimation(false /* expanding */);
}
@@ -244,6 +256,7 @@
}
mAfterCollapse = null;
+ mFadeBubblesDuringCollapse = false;
};
}
@@ -253,7 +266,7 @@
== LAYOUT_DIRECTION_RTL;
// Animate each bubble individually, since each path will end in a different spot.
- animationsForChildrenFromIndex(0, (index, animation) -> {
+ animationsForChildrenFromIndex(0, mFadeBubblesDuringCollapse, (index, animation) -> {
final View bubble = mLayout.getChildAt(index);
// Start a path at the bubble's current position.
@@ -274,9 +287,14 @@
// of the screen where the bubble will be stacked.
path.lineTo(stackedX, p.y);
+ // The overflow should animate to the collapse point, so 0 offset.
+ final boolean isOverflow = bubble instanceof BadgedImageView
+ && BubbleOverflow.KEY.equals(((BadgedImageView) bubble).getKey());
+ final float offsetY = isOverflow
+ ? 0
+ : Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx;
// Then, draw a line down to the stack position.
- path.lineTo(stackedX, mCollapsePoint.y
- + Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx);
+ path.lineTo(stackedX, mCollapsePoint.y + offsetY);
}
// The lead bubble should be the bubble with the longest distance to travel when we're
@@ -362,6 +380,9 @@
mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
private void springBubbleTo(View bubble, float x, float y) {
@@ -505,8 +526,12 @@
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+ boolean isOverflow = (view instanceof BadgedImageView)
+ && BubbleOverflow.KEY.equals(((BadgedImageView) view).getKey());
return new SpringForce()
- .setDampingRatio(DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
+ .setDampingRatio(isOverflow
+ ? DAMPING_RATIO_OVERFLOW_BOUNCY
+ : DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
new file mode 100644
index 0000000..2a44f04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.bubbles.animation
+
+/** Utils related to the fling to dismiss animation. */
+object FlingToDismissUtils {
+
+ /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f
+ /**
+ * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in
+ * portrait.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f
+ /**
+ * The target width surrounding the dismiss target on a large width screen, e.g. tablet in
+ * landscape.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f
+
+ /** Returns the dismiss target width for the specified [screenWidthPx]. */
+ @JvmStatic
+ fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when {
+ screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE
+ screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM
+ else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index f3cc514..ed00da8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -204,6 +204,13 @@
return animationForChild(mLayout.getChildAt(index));
}
+
+ protected MultiAnimationStarter animationsForChildrenFromIndex(
+ int startIndex, ChildAnimationConfigurator configurator) {
+ return animationsForChildrenFromIndex(startIndex, /* fadeChildren= */ false,
+ configurator);
+ }
+
/**
* Returns a {@link MultiAnimationStarter} whose startAll method will start the physics
* animations for all children from startIndex onward. The provided configurator will be
@@ -211,14 +218,16 @@
* animation appropriately.
*/
protected MultiAnimationStarter animationsForChildrenFromIndex(
- int startIndex, ChildAnimationConfigurator configurator) {
+ int startIndex, boolean fadeChildren, ChildAnimationConfigurator configurator) {
final Set<DynamicAnimation.ViewProperty> allAnimatedProperties = new HashSet<>();
final List<PhysicsPropertyAnimator> allChildAnims = new ArrayList<>();
// Retrieve the animator for each child, ask the configurator to configure it, then save
// it and the properties it chose to animate.
for (int i = startIndex; i < mLayout.getChildCount(); i++) {
- final PhysicsPropertyAnimator anim = animationForChildAtIndex(i);
+ final PhysicsPropertyAnimator anim = fadeChildren
+ ? animationForChildAtIndex(i).alpha(0)
+ : animationForChildAtIndex(i);
configurator.configureAnimationForChildAtIndex(i, anim);
allAnimatedProperties.addAll(anim.getAnimatedProperties());
allChildAnims.add(anim);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index aad2683..e487328 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.ContentResolver;
import android.content.res.Resources;
@@ -851,6 +852,15 @@
if (mLayout != null) {
Resources res = mLayout.getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ updateFlingToDismissTargetWidth();
+ }
+ }
+
+ private void updateFlingToDismissTargetWidth() {
+ if (mLayout != null && mMagnetizedStack != null) {
+ int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedStack.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
}
@@ -1022,23 +1032,8 @@
};
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ updateFlingToDismissTargetWidth();
}
-
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float minVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_fling_min_velocity",
- mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
- final float maxVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_stick_max_velocity",
- mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
- final float targetWidth = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_target_width_percent",
- mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
- mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
- mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
- mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
return mMagnetizedStack;
}
@@ -1053,7 +1048,7 @@
* property directly to move the first bubble and cause the stack to 'follow' to the new
* location.
*
- * This could also be achieved by simply animating the first bubble view and adding an update
+ * <p>This could also be achieved by simply animating the first bubble view and adding an update
* listener to dispatch movement to the rest of the stack. However, this would require
* duplication of logic in that update handler - it's simpler to keep all logic contained in the
* {@link #moveFirstBubbleWithStackFollowing} method.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 689323b..7f34ee0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -78,34 +78,37 @@
mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev != null) {
// We need to be Z ordered on top in order for alpha animations to work.
- mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(true);
- mExpandedBubble.getBubbleBarExpandedView().setAnimating(true);
+ bbev.setSurfaceZOrderedOnTop(true);
+ bbev.setAnimating(true);
}
}
@Override
public void onAnimationEnd(Animator animation) {
- if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev != null) {
// The surface needs to be Z ordered on top for alpha values to work on the
// TaskView, and if we're temporarily hidden, we are still on the screen
// with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
// = 0f remains in effect.
if (mIsExpanded) {
- mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(false);
+ bbev.setSurfaceZOrderedOnTop(false);
}
- mExpandedBubble.getBubbleBarExpandedView().setContentVisibility(mIsExpanded);
- mExpandedBubble.getBubbleBarExpandedView().setAnimating(false);
+ bbev.setContentVisibility(mIsExpanded);
+ bbev.setAnimating(false);
}
}
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev != null) {
float alpha = (float) valueAnimator.getAnimatedValue();
- mExpandedBubble.getBubbleBarExpandedView().setTaskViewAlpha(alpha);
- mExpandedBubble.getBubbleBarExpandedView().setAlpha(alpha);
+ bbev.setTaskViewAlpha(alpha);
+ bbev.setAlpha(alpha);
}
});
}
@@ -116,11 +119,8 @@
public void animateExpansion(BubbleViewProvider expandedBubble,
@Nullable Runnable afterAnimation) {
mExpandedBubble = expandedBubble;
- if (mExpandedBubble == null) {
- return;
- }
- BubbleBarExpandedView bev = mExpandedBubble.getBubbleBarExpandedView();
- if (bev == null) {
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
return;
}
mIsExpanded = true;
@@ -129,11 +129,11 @@
mExpandedViewContainerMatrix.setScaleY(0f);
updateExpandedView();
- bev.setAnimating(true);
- bev.setContentVisibility(false);
- bev.setAlpha(0f);
- bev.setTaskViewAlpha(0f);
- bev.setVisibility(VISIBLE);
+ bbev.setAnimating(true);
+ bbev.setContentVisibility(false);
+ bbev.setAlpha(0f);
+ bbev.setTaskViewAlpha(0f);
+ bbev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
@@ -143,7 +143,7 @@
bubbleBarPosition.x,
bubbleBarPosition.y);
- bev.setAnimationMatrix(mExpandedViewContainerMatrix);
+ bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
mExpandedViewAlphaAnimator.start();
@@ -156,13 +156,12 @@
AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
mScaleInSpringConfig)
.addUpdateListener((target, values) -> {
- mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(
- mExpandedViewContainerMatrix);
+ bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
})
.withEndActions(() -> {
- bev.setAnimationMatrix(null);
+ bbev.setAnimationMatrix(null);
updateExpandedView();
- bev.setSurfaceZOrderedOnTop(false);
+ bbev.setSurfaceZOrderedOnTop(false);
if (afterAnimation != null) {
afterAnimation.run();
}
@@ -177,7 +176,8 @@
*/
public void animateCollapse(Runnable endRunnable) {
mIsExpanded = false;
- if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
Log.w(TAG, "Trying to animate collapse without a bubble");
return;
}
@@ -196,17 +196,10 @@
EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT),
mScaleOutSpringConfig)
.addUpdateListener((target, values) -> {
- if (mExpandedBubble != null
- && mExpandedBubble.getBubbleBarExpandedView() != null) {
- mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(
- mExpandedViewContainerMatrix);
- }
+ bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
})
.withEndActions(() -> {
- if (mExpandedBubble != null
- && mExpandedBubble.getBubbleBarExpandedView() != null) {
- mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(null);
- }
+ bbev.setAnimationMatrix(null);
if (endRunnable != null) {
endRunnable.run();
}
@@ -223,12 +216,20 @@
mExpandedViewAlphaAnimator.cancel();
}
+ private @Nullable BubbleBarExpandedView getExpandedView() {
+ BubbleViewProvider bubble = mExpandedBubble;
+ if (bubble != null) {
+ return bubble.getBubbleBarExpandedView();
+ }
+ return null;
+ }
+
private void updateExpandedView() {
- if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
Log.w(TAG, "Trying to update the expanded view without a bubble");
return;
}
- BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
final int padding = mPositioner.getBubbleBarExpandedViewPadding();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 79f188a..d073f1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -386,4 +386,11 @@
setContentVisibility(mIsContentVisible);
}
}
+
+ /**
+ * Check whether the view is animating
+ */
+ public boolean isAnimating() {
+ return mIsAnimating;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
new file mode 100644
index 0000000..4ea18f7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 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.bubbles.bar
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.View
+import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.common.bubbles.DismissView
+import com.android.wm.shell.common.bubbles.RelativeTouchListener
+
+/** Controller for handling drag interactions with [BubbleBarExpandedView] */
+class BubbleBarExpandedViewDragController(
+ private val expandedView: BubbleBarExpandedView,
+ private val dismissView: DismissView,
+ private val onDismissed: () -> Unit
+) {
+
+ init {
+ expandedView.handleView.setOnTouchListener(HandleDragListener())
+ }
+
+ private fun finishDrag(x: Float, y: Float, viewInitialX: Float, viewInitialY: Float) {
+ val dismissCircleBounds = Rect().apply { dismissView.circle.getBoundsOnScreen(this) }
+ if (dismissCircleBounds.contains(x.toInt(), y.toInt())) {
+ onDismissed()
+ } else {
+ resetExpandedViewPosition(viewInitialX, viewInitialY)
+ }
+ dismissView.hide()
+ }
+
+ private fun resetExpandedViewPosition(initialX: Float, initialY: Float) {
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ expandedView.isAnimating = true
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ expandedView.isAnimating = false
+ }
+ }
+ expandedView
+ .animate()
+ .translationX(initialX)
+ .translationY(initialY)
+ .setDuration(RESET_POSITION_ANIM_DURATION)
+ .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
+ .setListener(listener)
+ .start()
+ }
+
+ private inner class HandleDragListener : RelativeTouchListener() {
+
+ private val expandedViewRestPosition = PointF()
+
+ override fun onDown(v: View, ev: MotionEvent): Boolean {
+ // While animating, don't allow new touch events
+ if (expandedView.isAnimating) {
+ return false
+ }
+ expandedViewRestPosition.x = expandedView.translationX
+ expandedViewRestPosition.y = expandedView.translationY
+ return true
+ }
+
+ override fun onMove(
+ v: View,
+ ev: MotionEvent,
+ viewInitialX: Float,
+ viewInitialY: Float,
+ dx: Float,
+ dy: Float
+ ) {
+ expandedView.translationX = expandedViewRestPosition.x + dx
+ expandedView.translationY = expandedViewRestPosition.y + dy
+ dismissView.show()
+ }
+
+ override fun onUp(
+ v: View,
+ ev: MotionEvent,
+ viewInitialX: Float,
+ viewInitialY: Float,
+ dx: Float,
+ dy: Float,
+ velX: Float,
+ velY: Float
+ ) {
+ finishDrag(ev.rawX, ev.rawY, expandedViewRestPosition.x, expandedViewRestPosition.y)
+ }
+
+ override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
+ resetExpandedViewPosition(expandedViewRestPosition.x, expandedViewRestPosition.y)
+ dismissView.hide()
+ }
+ }
+
+ companion object {
+ const val RESET_POSITION_ANIM_DURATION = 300L
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 8f11253..bdb0e20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -21,19 +21,29 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowManager;
import android.widget.FrameLayout;
+import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
+import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.bubbles.DeviceConfig;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
+import kotlin.Unit;
+
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -57,7 +67,11 @@
@Nullable
private BubbleViewProvider mExpandedBubble;
+ @Nullable
private BubbleBarExpandedView mExpandedView;
+ @Nullable
+ private BubbleBarExpandedViewDragController mDragController;
+ private DismissView mDismissView;
private @Nullable Consumer<String> mUnBubbleConversationCallback;
// TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
@@ -95,13 +109,16 @@
mScrimView.setBackgroundDrawable(new ColorDrawable(
getResources().getColor(android.R.color.system_neutral1_1000)));
+ setUpDismissView();
+
setOnClickListener(view -> hideMenuOrCollapse());
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mPositioner.update();
+ WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager)));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
}
@@ -111,7 +128,7 @@
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
if (mExpandedView != null) {
- mEducationViewController.hideManageEducation(/* animated = */ false);
+ mEducationViewController.hideEducation(/* animated = */ false);
removeView(mExpandedView);
mExpandedView = null;
}
@@ -171,7 +188,9 @@
mExpandedView.setListener(new BubbleBarExpandedView.Listener() {
@Override
public void onTaskCreated() {
- mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ if (mEducationViewController != null && mExpandedView != null) {
+ mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ }
}
@Override
@@ -187,9 +206,20 @@
}
});
+ mDragController = new BubbleBarExpandedViewDragController(mExpandedView, mDismissView,
+ () -> {
+ mBubbleController.dismissBubble(mExpandedBubble.getKey(),
+ Bubbles.DISMISS_USER_GESTURE);
+ return Unit.INSTANCE;
+ });
+
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
}
+ if (mEducationViewController.isEducationVisible()) {
+ mEducationViewController.hideEducation(/* animated = */ true);
+ }
+
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
@@ -210,24 +240,52 @@
public void collapse() {
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
- mEducationViewController.hideManageEducation(/* animated = */ true);
+ mEducationViewController.hideEducation(/* animated = */ true);
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
+ mDragController = null;
setTouchDelegate(null);
showScrim(false);
}
+ /**
+ * Show bubble bar user education relative to the reference position.
+ * @param position the reference position in Screen coordinates.
+ */
+ public void showUserEducation(Point position) {
+ mEducationViewController.showStackEducation(position, /* root = */ this, () -> {
+ // When the user education is clicked hide it and expand the selected bubble
+ mEducationViewController.hideEducation(/* animated = */ true, () -> {
+ mBubbleController.expandStackWithSelectedBubble();
+ return Unit.INSTANCE;
+ });
+ return Unit.INSTANCE;
+ });
+ }
+
/** Sets the function to call to un-bubble the given conversation. */
public void setUnBubbleConversationCallback(
@Nullable Consumer<String> unBubbleConversationCallback) {
mUnBubbleConversationCallback = unBubbleConversationCallback;
}
+ private void setUpDismissView() {
+ if (mDismissView != null) {
+ removeView(mDismissView);
+ }
+ mDismissView = new DismissView(getContext());
+ DismissViewUtils.setup(mDismissView);
+ int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation);
+
+ addView(mDismissView);
+ mDismissView.setElevation(elevation);
+ }
+
/** Hides the current modal education/menu view, expanded view or collapses the bubble stack */
private void hideMenuOrCollapse() {
- if (mEducationViewController.isManageEducationVisible()) {
- mEducationViewController.hideManageEducation(/* animated = */ true);
+ if (mEducationViewController.isEducationVisible()) {
+ mEducationViewController.hideEducation(/* animated = */ true);
} else if (isExpanded() && mExpandedView != null) {
mExpandedView.hideMenuOrCollapse();
} else {
@@ -275,7 +333,7 @@
*/
private void getTouchableRegion(Region outRegion) {
mTempRect.setEmpty();
- if (mIsExpanded) {
+ if (mIsExpanded || mEducationViewController.isEducationVisible()) {
getBoundsOnScreen(mTempRect);
outRegion.op(mTempRect, Region.Op.UNION);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7b39c6f..ee552ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -15,23 +15,34 @@
*/
package com.android.wm.shell.bubbles.bar
+import android.annotation.LayoutRes
import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.Log
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.core.view.doOnLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import com.android.wm.shell.bubbles.BubbleEducationController
import com.android.wm.shell.bubbles.BubbleViewProvider
import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.common.bubbles.BubblePopupDrawable
import com.android.wm.shell.common.bubbles.BubblePopupView
+import kotlin.math.roundToInt
/** Manages bubble education presentation and animation */
class BubbleEducationViewController(private val context: Context, private val listener: Listener) {
interface Listener {
- fun onManageEducationVisibilityChanged(isVisible: Boolean)
+ fun onEducationVisibilityChanged(isVisible: Boolean)
}
private var rootView: ViewGroup? = null
@@ -45,61 +56,112 @@
)
}
+ private val scrimView by lazy {
+ View(context).apply {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setOnClickListener { hideEducation(animated = true) }
+ }
+ }
+
private val controller by lazy { BubbleEducationController(context) }
/** Whether the education view is visible or being animated */
- val isManageEducationVisible: Boolean
+ val isEducationVisible: Boolean
get() = educationView != null && rootView != null
/**
+ * Hide the current education view if visible
+ *
+ * @param animated whether should hide with animation
+ */
+ @JvmOverloads
+ fun hideEducation(animated: Boolean, endActions: () -> Unit = {}) {
+ log { "hideEducation animated: $animated" }
+
+ if (animated) {
+ animateTransition(show = false) {
+ cleanUp()
+ endActions()
+ listener.onEducationVisibilityChanged(isVisible = false)
+ }
+ } else {
+ cleanUp()
+ endActions()
+ listener.onEducationVisibilityChanged(isVisible = false)
+ }
+ }
+
+ /**
+ * Show bubble bar stack user education.
+ *
+ * @param position the reference position for the user education in Screen coordinates.
+ * @param root the view to show user education in.
+ * @param educationClickHandler the on click handler for the user education view
+ */
+ fun showStackEducation(position: Point, root: ViewGroup, educationClickHandler: () -> Unit) {
+ hideEducation(animated = false)
+ log { "showStackEducation at: $position" }
+
+ educationView =
+ createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+ setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
+ setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+ updateEducationPosition(view = this, position, root)
+ val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
+ doOnLayout {
+ it.pivotX = it.width - arrowToEdgeOffset
+ it.pivotY = it.height.toFloat()
+ }
+ setOnClickListener { educationClickHandler() }
+ }
+
+ rootView = root
+ animator = createAnimator()
+
+ root.addView(scrimView)
+ root.addView(educationView)
+ animateTransition(show = true) {
+ controller.hasSeenStackEducation = true
+ listener.onEducationVisibilityChanged(isVisible = true)
+ }
+ }
+
+ /**
* Show manage bubble education if hasn't been shown before
*
* @param bubble the bubble used for the manage education check
* @param root the view to show manage education in
*/
fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
+ log { "maybeShowManageEducation bubble: $bubble" }
if (!controller.shouldShowManageEducation(bubble)) return
showManageEducation(root)
}
/**
- * Hide the manage education view if visible
- *
- * @param animated whether should hide with animation
- */
- fun hideManageEducation(animated: Boolean) {
- rootView?.let {
- fun cleanUp() {
- it.removeView(educationView)
- rootView = null
- listener.onManageEducationVisibilityChanged(isVisible = false)
- }
-
- if (animated) {
- animateTransition(show = false, ::cleanUp)
- } else {
- cleanUp()
- }
- }
- }
-
- /**
* Show manage education with animation
*
* @param root the view to show manage education in
*/
private fun showManageEducation(root: ViewGroup) {
- hideManageEducation(animated = false)
- if (educationView == null) {
- val eduView = createEducationView(root)
- educationView = eduView
- animator = createAnimation(eduView)
- }
- root.addView(educationView)
+ hideEducation(animated = false)
+ log { "showManageEducation" }
+
+ educationView =
+ createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+ pivotY = 0f
+ doOnLayout { it.pivotX = it.width / 2f }
+ setOnClickListener { hideEducation(animated = true) }
+ }
+
rootView = root
+ animator = createAnimator()
+
+ root.addView(scrimView)
+ root.addView(educationView)
animateTransition(show = true) {
controller.hasSeenManageEducation = true
- listener.onManageEducationVisibilityChanged(isVisible = true)
+ listener.onEducationVisibilityChanged(isVisible = true)
}
}
@@ -110,39 +172,75 @@
* @param endActions a closure to be called when the animation completes
*/
private fun animateTransition(show: Boolean, endActions: () -> Unit) {
- animator?.let { animator ->
- animator
- .spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
- .spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
- .spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
- .withEndActions(endActions)
- .start()
- } ?: endActions()
+ animator
+ ?.spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
+ ?.spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
+ ?.spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
+ ?.withEndActions(endActions)
+ ?.start()
+ ?: endActions()
}
- private fun createEducationView(root: ViewGroup): BubblePopupView {
- val view =
- LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false)
- as BubblePopupView
+ /** Remove education view from the root and clean up all relative properties */
+ private fun cleanUp() {
+ log { "cleanUp" }
+ rootView?.removeView(educationView)
+ rootView?.removeView(scrimView)
+ educationView = null
+ rootView = null
+ animator = null
+ }
- return view.apply {
- setup()
- alpha = 0f
- pivotY = 0f
- scaleX = EDU_SCALE_HIDDEN
- scaleY = EDU_SCALE_HIDDEN
- doOnLayout { it.pivotX = it.width / 2f }
- setOnClickListener { hideManageEducation(animated = true) }
+ /**
+ * Create education view by inflating layout provided.
+ *
+ * @param layout layout resource id to inflate. The root view should be [BubblePopupView]
+ * @param root view group to use as root for inflation, is not attached to root
+ */
+ private fun createEducationView(@LayoutRes layout: Int, root: ViewGroup): BubblePopupView {
+ val view = LayoutInflater.from(context).inflate(layout, root, false) as BubblePopupView
+ view.setup()
+ view.alpha = 0f
+ view.scaleX = EDU_SCALE_HIDDEN
+ view.scaleY = EDU_SCALE_HIDDEN
+ return view
+ }
+
+ /** Create animator for the user education transitions */
+ private fun createAnimator(): PhysicsAnimator<BubblePopupView>? {
+ return educationView?.let {
+ PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
}
}
- private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> {
- val animator = PhysicsAnimator.getInstance(view)
- animator.setDefaultSpringConfig(springConfig)
- return animator
+ /**
+ * Update user education view position relative to the reference position
+ *
+ * @param view the user education view to layout
+ * @param position the reference position in Screen coordinates
+ * @param root the root view to use for the layout
+ */
+ private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
+ val rootBounds = Rect()
+ // Get root bounds on screen as position is in screen coordinates
+ root.getBoundsOnScreen(rootBounds)
+ // Get the offset to the arrow from the edge of the education view
+ val arrowToEdgeOffset =
+ view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
+ ?: 0
+ // Calculate education view margins
+ val params = view.layoutParams as FrameLayout.LayoutParams
+ params.bottomMargin = rootBounds.bottom - position.y
+ params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+ view.layoutParams = params
+ }
+
+ private fun log(msg: () -> String) {
+ if (DEBUG_USER_EDUCATION) Log.d(TAG, msg())
}
companion object {
+ private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationViewController" else TAG_BUBBLES
private const val EDU_SCALE_HIDDEN = 0.5f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
index 85aaa8e..4206d93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
@@ -29,4 +29,7 @@
* When this is `false`, bubbles will be floating.
*/
val isBubbleBarEnabled: Boolean
+
+ /** Refreshes the current value of [isBubbleBarEnabled]. */
+ fun refresh()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index 9d8b9a6..e1dea3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -22,6 +22,13 @@
object ProdBubbleProperties : BubbleProperties {
// TODO(b/256873975) Should use proper flag when available to shell/launcher
- override val isBubbleBarEnabled =
- SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+ private var _isBubbleBarEnabled =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+
+ override val isBubbleBarEnabled
+ get() = _isBubbleBarEnabled
+
+ override fun refresh() {
+ _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 72702e7..b828aac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Slog;
import android.view.IDisplayChangeWindowCallback;
import android.view.IDisplayChangeWindowController;
@@ -40,6 +41,7 @@
*/
public class DisplayChangeController {
private static final String TAG = DisplayChangeController.class.getSimpleName();
+ private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange";
private final ShellExecutor mMainExecutor;
private final IWindowManager mWmService;
@@ -81,9 +83,15 @@
/** Query all listeners for changes that should happen on display change. */
void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginSection("dispatchOnDisplayChange");
+ }
for (OnDisplayChangingListener c : mDisplayChangeListener) {
c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endSection();
+ }
}
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -94,6 +102,10 @@
callback.continueDisplayChange(t);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to continue handling display change", e);
+ } finally {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
}
}
@@ -103,6 +115,9 @@
@Override
public void onDisplayChange(int displayId, int fromRotation, int toRotation,
DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
mMainExecutor.execute(() -> DisplayChangeController.this
.onDisplayChange(displayId, fromRotation, toRotation,
newDisplayAreaInfo, callback));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
index ec344d3..86f00b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -23,6 +23,7 @@
import android.view.SurfaceControl;
import android.view.View;
+import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
/** Utils class for simplfy InteractionJank trancing call */
@@ -31,11 +32,11 @@
/**
* Begin a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
* @param view the view to trace
* @param tag the tag to distinguish different flow of same type CUJ.
*/
- public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ public static void beginTracing(@CujType int cujType,
@NonNull View view, @Nullable String tag) {
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
@@ -48,12 +49,12 @@
/**
* Begin a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
* @param context the context
* @param surface the surface to trace
* @param tag the tag to distinguish different flow of same type CUJ.
*/
- public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ public static void beginTracing(@CujType int cujType,
@NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
final InteractionJankMonitor.Configuration.Builder builder =
InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
@@ -66,18 +67,18 @@
/**
* End a trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
*/
- public static void endTracing(@InteractionJankMonitor.CujType int cujType) {
+ public static void endTracing(@CujType int cujType) {
InteractionJankMonitor.getInstance().end(cujType);
}
/**
* Cancel the trace session.
*
- * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param cujType the specific {@link CujType}.
*/
- public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) {
+ public static void cancelTracing(@CujType int cujType) {
InteractionJankMonitor.getInstance().cancel(cujType);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
index 7af0389..6519eee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -1 +1,2 @@
madym@google.com
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 5e42782..1c74f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -203,7 +203,7 @@
+ "SystemWindow:" + view);
return null;
}
- return root.getFocusGrantToken();
+ return root.getInputTransferToken();
}
private class PerDisplay {
@@ -322,13 +322,12 @@
}
@Override
- public void remove(android.view.IWindow window) throws RemoteException {
- super.remove(window);
+ public void remove(IBinder clientToken) throws RemoteException {
+ super.remove(clientToken);
synchronized(this) {
- IBinder token = window.asBinder();
- new SurfaceControl.Transaction().remove(mLeashForWindow.get(token))
+ new SurfaceControl.Transaction().remove(mLeashForWindow.get(clientToken))
.apply();
- mLeashForWindow.remove(token);
+ mLeashForWindow.remove(clientToken);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 931cf0c..c6c9b35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -94,6 +94,10 @@
mCurrentIcon = icon;
// Remove old image while waiting for the new one to load.
mIconImageView.setImageDrawable(null);
+ if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // Disallow loading icon from content URI
+ return;
+ }
icon.loadDrawableAsync(mContext, d -> {
// The image hasn't been set any other way and the drawable belongs to the most
// recently set Icon.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index 8142347..fc627a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -35,6 +35,7 @@
public boolean expandedChanged;
public boolean expanded;
+ public boolean shouldShowEducation;
@Nullable
public String selectedBubbleKey;
@Nullable
@@ -61,6 +62,7 @@
public BubbleBarUpdate(Parcel parcel) {
expandedChanged = parcel.readBoolean();
expanded = parcel.readBoolean();
+ shouldShowEducation = parcel.readBoolean();
selectedBubbleKey = parcel.readString();
addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
BubbleInfo.class);
@@ -95,6 +97,7 @@
return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ " expanded=" + expanded
+ " selectedBubbleKey=" + selectedBubbleKey
+ + " shouldShowEducation=" + shouldShowEducation
+ " addedBubble=" + addedBubble
+ " updatedBubble=" + updatedBubble
+ " suppressedBubbleKey=" + suppressedBubbleKey
@@ -114,6 +117,7 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeBoolean(expandedChanged);
parcel.writeBoolean(expanded);
+ parcel.writeBoolean(shouldShowEducation);
parcel.writeString(selectedBubbleKey);
parcel.writeParcelable(addedBubble, flags);
parcel.writeParcelable(updatedBubble, flags);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
new file mode 100644
index 0000000..0329b8d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.common.bubbles;
+
+/**
+ * Constants shared between bubbles in shell & things we have to do for bubbles in launcher.
+ */
+public class BubbleConstants {
+
+ /** The alpha for the scrim shown when bubbles are expanded. */
+ public static float BUBBLE_EXPANDED_SCRIM_ALPHA = .32f;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
index 1fd22d0a..887af17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -31,7 +31,7 @@
import kotlin.properties.Delegates
/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */
-class BubblePopupDrawable(private val config: Config) : Drawable() {
+class BubblePopupDrawable(val config: Config) : Drawable() {
/** The direction of the arrow in the popup drawable */
enum class ArrowDirection {
UP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
index f8a4946..444fbf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
@@ -29,7 +29,8 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
- private var popupDrawable: BubblePopupDrawable? = null
+ var popupDrawable: BubblePopupDrawable? = null
+ private set
/**
* Sets up the popup drawable with the config provided. Required to remove dependency on local
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
index d275a0b..9094739 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
@@ -49,6 +49,8 @@
* @see [setup] method
*/
data class Config(
+ /** The resource id to set on the dismiss target circle view */
+ val dismissViewResId: Int,
/** dimen resource id of the dismiss target circle view size */
@DimenRes val targetSizeResId: Int,
/** dimen resource id of the icon size in the dismiss target */
@@ -121,6 +123,7 @@
setBackgroundDrawable(gradientDrawable)
// Setup DismissCircleView
+ circle.id = config.dismissViewResId
circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId)
val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId)
circle.layoutParams = LayoutParams(targetSize, targetSize,
@@ -164,7 +167,11 @@
animator
.spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
spring)
- .withEndActions({ setVisibility(View.INVISIBLE) })
+ .withEndActions({
+ visibility = View.INVISIBLE
+ circle.scaleX = 1f
+ circle.scaleY = 1f
+ })
.start()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
index cc37bd3a4..4e55ba2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -78,8 +78,15 @@
velY: Float
)
+ open fun onCancel(
+ v: View,
+ ev: MotionEvent,
+ viewInitialX: Float,
+ viewInitialY: Float
+ ) {}
+
/** The raw coordinates of the last ACTION_DOWN event. */
- private val touchDown = PointF()
+ private var touchDown: PointF? = null
/** The coordinates of the view, at the time of the last ACTION_DOWN event. */
private val viewPositionOnTouchDown = PointF()
@@ -91,12 +98,11 @@
private var performedLongClick = false
- @Suppress("UNCHECKED_CAST")
override fun onTouch(v: View, ev: MotionEvent): Boolean {
addMovement(ev)
- val dx = ev.rawX - touchDown.x
- val dy = ev.rawY - touchDown.y
+ val dx = touchDown?.let { ev.rawX - it.x } ?: 0f
+ val dy = touchDown?.let { ev.rawY - it.y } ?: 0f
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
@@ -108,11 +114,11 @@
// last gesture.
touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
- touchDown.set(ev.rawX, ev.rawY)
+ touchDown = PointF(ev.rawX, ev.rawY)
viewPositionOnTouchDown.set(v.translationX, v.translationY)
performedLongClick = false
- v.handler.postDelayed({
+ v.handler?.postDelayed({
if (v.isLongClickable) {
performedLongClick = v.performLongClick()
}
@@ -120,9 +126,10 @@
}
MotionEvent.ACTION_MOVE -> {
+ if (touchDown == null) return false
if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
movedEnough = true
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
}
if (movedEnough) {
@@ -131,6 +138,7 @@
}
MotionEvent.ACTION_UP -> {
+ if (touchDown == null) return false
if (movedEnough) {
velocityTracker.computeCurrentVelocity(1000 /* units */)
onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
@@ -138,17 +146,21 @@
} else if (!performedLongClick) {
v.performClick()
} else {
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
}
velocityTracker.clear()
movedEnough = false
+ touchDown = null
}
MotionEvent.ACTION_CANCEL -> {
- v.handler.removeCallbacksAndMessages(null)
+ if (touchDown == null) return false
+ v.handler?.removeCallbacksAndMessages(null)
velocityTracker.clear()
movedEnough = false
+ touchDown = null
+ onCancel(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 3b32b6c..8b6c7b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -126,12 +126,22 @@
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
+ // the size of the current bounds relative to the max size spec
+ private float mBoundsScale;
+
public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
@NonNull PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
mSizeSpecSource = sizeSpecSource;
mPipDisplayLayoutState = pipDisplayLayoutState;
+
+ // Update the relative proportion of the bounds compared to max possible size. Max size
+ // spec takes the aspect ratio of the bounds into account, so both width and height
+ // scale by the same factor.
+ addPipExclusionBoundsChangeCallback((bounds) -> {
+ mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+ });
}
/** Reloads the resources. */
@@ -160,6 +170,15 @@
return new Rect(mBounds);
}
+ /**
+ * Get the scale of the current bounds relative to the maximum size possible.
+ *
+ * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}.
+ */
+ public float getBoundsScale() {
+ return mBoundsScale;
+ }
+
/** Returns the current movement bounds. */
@NonNull
public Rect getMovementBounds() {
@@ -239,7 +258,7 @@
ActivityTaskManager.getService().onPictureInPictureStateChanged(
new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
);
- } catch (RemoteException e) {
+ } catch (RemoteException | IllegalStateException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Unable to set alert PiP state change.", TAG);
}
@@ -622,6 +641,9 @@
pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
+ pw.println(innerPrefix + "mMinSize=" + mMinSize);
+ pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
+ pw.println(innerPrefix + "mBoundsScale" + mBoundsScale);
if (mPipReentryState == null) {
pw.println(innerPrefix + "mPipReentryState=null");
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 84feb03..1e30d8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -21,13 +21,13 @@
import android.content.ComponentName
import android.content.Context
import android.os.RemoteException
-import android.os.SystemProperties
import android.util.DisplayMetrics
import android.util.Log
import android.util.Pair
import android.util.TypedValue
import android.window.TaskSnapshot
import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup
import kotlin.math.abs
@@ -37,7 +37,6 @@
// Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
private const val EPSILON = 1e-7
- private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation"
/**
* @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
@@ -129,9 +128,7 @@
@JvmStatic
fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? {
return if (taskId <= 0) null else try {
- ActivityTaskManager.getService().getTaskSnapshot(
- taskId, isLowResolution, false /* takeSnapshotIfNeeded */
- )
+ ActivityTaskManager.getService().getTaskSnapshot(taskId, isLowResolution)
} catch (e: RemoteException) {
Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e)
null
@@ -140,5 +137,5 @@
@JvmStatic
val isPip2ExperimentEnabled: Boolean
- get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false)
+ get() = Flags.enablePip2Implementation()
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index ec26800..999da24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -68,24 +68,33 @@
};
private final Paint mPaint = new Paint();
- private final int mWidth;
- private final int mHeight;
- private final int mTouchingWidth;
- private final int mTouchingHeight;
+ private int mWidth;
+ private int mHeight;
+ private int mTouchingWidth;
+ private int mTouchingHeight;
private int mCurrentWidth;
private int mCurrentHeight;
private AnimatorSet mAnimator;
private boolean mTouching;
private boolean mHovering;
- private final int mHoveringWidth;
- private final int mHoveringHeight;
+ private int mHoveringWidth;
+ private int mHoveringHeight;
+ private boolean mIsLeftRightSplit;
public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
mPaint.setAntiAlias(true);
- mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
- mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
+ updateDimens();
+ }
+
+ private void updateDimens() {
+ mWidth = getResources().getDimensionPixelSize(mIsLeftRightSplit
+ ? R.dimen.split_divider_handle_height
+ : R.dimen.split_divider_handle_width);
+ mHeight = getResources().getDimensionPixelSize(mIsLeftRightSplit
+ ? R.dimen.split_divider_handle_width
+ : R.dimen.split_divider_handle_height);
mCurrentWidth = mWidth;
mCurrentHeight = mHeight;
mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
@@ -94,6 +103,11 @@
mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
}
+ void setIsLeftRightSplit(boolean isLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
+ updateDimens();
+ }
+
/** Sets touching state for this handle view. */
public void setTouching(boolean touching, boolean animate) {
if (touching == mTouching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
index 364bb65..834c15d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -47,6 +46,7 @@
private InvertedRoundedCornerDrawInfo mTopRightCorner;
private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
private InvertedRoundedCornerDrawInfo mBottomRightCorner;
+ private boolean mIsLeftRightSplit;
public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -98,8 +98,8 @@
return false;
}
- private boolean isLandscape() {
- return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ void setIsLeftRightSplit(boolean isLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
}
/**
@@ -134,7 +134,7 @@
}
private void calculateStartPos(Point outPos) {
- if (isLandscape()) {
+ if (mIsLeftRightSplit) {
// Place left corner at the right side of the divider bar.
outPos.x = isLeftCorner()
? getWidth() / 2 + mDividerWidth / 2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 1901e0b..f9a286e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -20,6 +20,15 @@
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -28,6 +37,8 @@
import android.view.Display;
import android.view.DisplayInfo;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
/**
@@ -195,6 +206,21 @@
}
}
+ /**
+ * Gets the SnapTarget corresponding to the given {@link SnapPosition}, or null if no such
+ * SnapTarget exists.
+ */
+ @Nullable
+ public SnapTarget findSnapTarget(@SnapPosition int snapPosition) {
+ for (SnapTarget t : mTargets) {
+ if (t.snapPosition == snapPosition) {
+ return t;
+ }
+ }
+
+ return null;
+ }
+
public float calculateDismissingFraction(int position) {
if (position < mFirstSplitTarget.position) {
return 1f - (float) (position - getStartInset())
@@ -263,7 +289,7 @@
private SnapTarget snap(int position, boolean hardDismiss) {
if (shouldApplyFreeSnapMode(position)) {
- return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+ return new SnapTarget(position, position, SNAP_TO_NONE);
}
int minIndex = -1;
float minDistance = Float.MAX_VALUE;
@@ -291,8 +317,7 @@
if (dockedSide == DOCKED_RIGHT) {
startPos += mInsets.left;
}
- mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
- 0.35f));
+ mTargets.add(new SnapTarget(startPos, startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision, dividerMax);
@@ -307,15 +332,15 @@
addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
- mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
+ mTargets.add(new SnapTarget(dividerMax, dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
}
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
int bottomPosition, int dividerMax) {
- maybeAddTarget(topPosition, topPosition - getStartInset());
+ maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70);
addMiddleTarget(isHorizontalDivision);
maybeAddTarget(bottomPosition,
- dividerMax - getEndInset() - (bottomPosition + mDividerSize));
+ dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30);
}
private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -349,16 +374,16 @@
* Adds a target at {@param position} but only if the area with size of {@param smallerSize}
* meets the minimal size requirement.
*/
- private void maybeAddTarget(int position, int smallerSize) {
+ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) {
if (smallerSize >= mMinimalSizeResizableTask) {
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, snapPosition));
}
}
private void addMiddleTarget(boolean isHorizontalDivision) {
int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SNAP_TO_50_50));
}
private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
@@ -372,7 +397,7 @@
position = mDisplayWidth - position - mInsets.right - mDividerSize;
}
}
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SNAP_TO_MINIMIZE));
}
public SnapTarget getMiddleTarget() {
@@ -412,6 +437,13 @@
}
/**
+ * Finds the {@link SnapPosition} nearest to the given position.
+ */
+ public int calculateNearestSnapPosition(int currentPosition) {
+ return snap(currentPosition, /* hardDismiss */ true).snapPosition;
+ }
+
+ /**
* Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
* if {@param increment} is negative and moves right otherwise.
*/
@@ -435,14 +467,6 @@
* Represents a snap target for the divider.
*/
public static class SnapTarget {
- public static final int FLAG_NONE = 0;
-
- /** If the divider reaches this value, the left/top task should be dismissed. */
- public static final int FLAG_DISMISS_START = 1;
-
- /** If the divider reaches this value, the right/bottom task should be dismissed */
- public static final int FLAG_DISMISS_END = 2;
-
/** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
public final int position;
@@ -452,7 +476,10 @@
*/
public final int taskPosition;
- public final int flag;
+ /**
+ * An int describing the placement of the divider in this snap target.
+ */
+ public final @SnapPosition int snapPosition;
public boolean isMiddleTarget;
@@ -462,14 +489,15 @@
*/
private final float distanceMultiplier;
- public SnapTarget(int position, int taskPosition, int flag) {
- this(position, taskPosition, flag, 1f);
+ public SnapTarget(int position, int taskPosition, @SnapPosition int snapPosition) {
+ this(position, taskPosition, snapPosition, 1f);
}
- public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
+ public SnapTarget(int position, int taskPosition, @SnapPosition int snapPosition,
+ float distanceMultiplier) {
this.position = position;
this.taskPosition = taskPosition;
- this.flag = flag;
+ this.snapPosition = snapPosition;
this.distanceMultiplier = distanceMultiplier;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 0b0c693..f801b0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -27,6 +26,8 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.provider.DeviceConfig;
@@ -65,12 +66,15 @@
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
+ private final Paint mPaint = new Paint();
+ private final Rect mBackgroundRect = new Rect();
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
private SplitWindowManager mSplitWindowManager;
private SurfaceControlViewHost mViewHost;
private DividerHandleView mHandle;
+ private DividerRoundedCorner mCorners;
private View mBackground;
private int mTouchElevation;
@@ -79,8 +83,11 @@
private int mStartPos;
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
+ private boolean mHideHandle;
private boolean mSetTouchRegion = true;
private int mLastDraggingPosition;
+ private int mHandleRegionWidth;
+ private int mHandleRegionHeight;
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -123,7 +130,7 @@
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
- if (isLandscape()) {
+ if (mSplitLayout.isLeftRightSplit()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
mContext.getString(R.string.accessibility_action_divider_left_full)));
if (snapAlgorithm.isFirstSplitTargetAvailable()) {
@@ -205,16 +212,24 @@
}
/** Sets up essential dependencies of the divider bar. */
- public void setup(
- SplitLayout layout,
- SplitWindowManager splitWindowManager,
- SurfaceControlViewHost viewHost,
- InsetsState insetsState) {
+ public void setup(SplitLayout layout, SplitWindowManager splitWindowManager,
+ SurfaceControlViewHost viewHost, InsetsState insetsState) {
mSplitLayout = layout;
mSplitWindowManager = splitWindowManager;
mViewHost = viewHost;
layout.getDividerBounds(mDividerBounds);
onInsetsChanged(insetsState, false /* animate */);
+
+ final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+ mHandle.setIsLeftRightSplit(isLeftRightSplit);
+ mCorners.setIsLeftRightSplit(isLeftRightSplit);
+
+ mHandleRegionWidth = getResources().getDimensionPixelSize(isLeftRightSplit
+ ? R.dimen.split_divider_handle_region_height
+ : R.dimen.split_divider_handle_region_width);
+ mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
+ ? R.dimen.split_divider_handle_region_width
+ : R.dimen.split_divider_handle_region_height);
}
void onInsetsChanged(InsetsState insetsState, boolean animate) {
@@ -255,30 +270,48 @@
super.onFinishInflate();
mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
- mBackground = findViewById(R.id.docked_divider_background);
+ mCorners = findViewById(R.id.docked_divider_rounded_corner);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
+ mHideHandle = false;
setOnTouchListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
+ setWillNotDraw(false);
+ mPaint.setColor(getResources().getColor(R.color.split_divider_background, null));
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mSetTouchRegion) {
- mTempRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
- mHandle.getBottom());
+ int startX = (mDividerBounds.width() - mHandleRegionWidth) / 2;
+ int startY = (mDividerBounds.height() - mHandleRegionHeight) / 2;
+ mTempRect.set(startX, startY, startX + mHandleRegionWidth,
+ startY + mHandleRegionHeight);
mSplitWindowManager.setTouchRegion(mTempRect);
mSetTouchRegion = false;
}
+
+ if (changed) {
+ boolean isHorizontalSplit = mSplitLayout.isLeftRightSplit();
+ int dividerSize = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ left = isHorizontalSplit ? (getWidth() - dividerSize) / 2 : 0;
+ top = isHorizontalSplit ? 0 : (getHeight() - dividerSize) / 2;
+ right = isHorizontalSplit ? left + dividerSize : getWidth();
+ bottom = isHorizontalSplit ? getHeight() : top + dividerSize;
+ mBackgroundRect.set(left, top, right, bottom);
+ }
}
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
return PointerIcon.getSystemIcon(getContext(),
- isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+ mSplitLayout.isLeftRightSplit() ? TYPE_HORIZONTAL_DOUBLE_ARROW
+ : TYPE_VERTICAL_DOUBLE_ARROW);
}
@Override
@@ -295,8 +328,8 @@
// moving divider bar and calculating dragging velocity.
event.setLocation(event.getRawX(), event.getRawY());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
- final boolean isLandscape = isLandscape();
- final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
+ final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+ final int touchPos = (int) (isLeftRightSplit ? event.getX() : event.getY());
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
@@ -328,7 +361,7 @@
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000 /* units */);
- final float velocity = isLandscape
+ final float velocity = isLeftRightSplit
? mVelocityTracker.getXVelocity()
: mVelocityTracker.getYVelocity();
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
@@ -410,6 +443,11 @@
.start();
}
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ canvas.drawRect(mBackgroundRect, mPaint);
+ }
+
@VisibleForTesting
void releaseHovering() {
mHandle.setHovering(false, true);
@@ -430,10 +468,11 @@
void setInteractive(boolean interactive, boolean hideHandle, String from) {
if (interactive == mInteractive) return;
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
- from);
+ "Set divider bar %s hide handle=%b from %s",
+ interactive ? "interactive" : "non-interactive", hideHandle, from);
mInteractive = interactive;
- if (!mInteractive && hideHandle && mMoving) {
+ mHideHandle = hideHandle;
+ if (!mInteractive && mHideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
@@ -443,11 +482,15 @@
mMoving = false;
}
releaseTouching();
- mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
+ mHandle.setVisibility(!mInteractive && mHideHandle ? View.INVISIBLE : View.VISIBLE);
}
- private boolean isLandscape() {
- return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ boolean isInteractive() {
+ return mInteractive;
+ }
+
+ boolean isHandleHidden() {
+ return mHideHandle;
}
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 755dba0..53caddb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -26,10 +26,10 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -59,6 +59,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -67,7 +68,10 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -77,7 +81,7 @@
* divide position changes.
*/
public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
-
+ private static final String TAG = "SplitLayout";
public static final int PARALLAX_NONE = 0;
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
@@ -116,15 +120,18 @@
@VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
private WindowContainerToken mWinToken1;
private WindowContainerToken mWinToken2;
- private int mDividePosition;
+ private int mDividerPosition;
private boolean mInitialized = false;
private boolean mFreezeDividerWindow = false;
+ private boolean mIsLargeScreen = false;
private int mOrientation;
private int mRotation;
private int mDensity;
private int mUiMode;
private final boolean mDimNonImeSide;
+ private final boolean mAllowLeftRightSplitInPortrait;
+ private boolean mIsLeftRightSplit;
private ValueAnimator mDividerFlingAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
@@ -136,6 +143,7 @@
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
mDensity = configuration.densityDpi;
+ mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
@@ -145,14 +153,17 @@
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+ final Resources res = mContext.getResources();
+ mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(res);
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ configuration);
+
updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
-
- mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
-
updateInvisibleRect();
}
@@ -268,24 +279,31 @@
}
int getDividePosition() {
- return mDividePosition;
+ return mDividerPosition;
+ }
+
+ /**
+ * Finds the {@link SnapPosition} nearest to the current divider position.
+ */
+ public int calculateCurrentSnapPosition() {
+ return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition);
}
/**
* Returns the divider position as a fraction from 0 to 1.
*/
public float getDividerPositionAsFraction() {
- return Math.min(1f, Math.max(0f, isLandscape()
+ return Math.min(1f, Math.max(0f, mIsLeftRightSplit
? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
: (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
}
private void updateInvisibleRect() {
mInvisibleBounds.set(mRootBounds.left, mRootBounds.top,
- isLandscape() ? mRootBounds.right / 2 : mRootBounds.right,
- isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2);
- mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
- isLandscape() ? 0 : mRootBounds.bottom);
+ mIsLeftRightSplit ? mRootBounds.right / 2 : mRootBounds.right,
+ mIsLeftRightSplit ? mRootBounds.bottom : mRootBounds.bottom / 2);
+ mInvisibleBounds.offset(mIsLeftRightSplit ? mRootBounds.right : 0,
+ mIsLeftRightSplit ? 0 : mRootBounds.bottom);
}
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
@@ -300,6 +318,7 @@
final int orientation = configuration.orientation;
final int density = configuration.densityDpi;
final int uiMode = configuration.uiMode;
+ final boolean wasLeftRightSplit = mIsLeftRightSplit;
if (mOrientation == orientation
&& mRotation == rotation
@@ -317,9 +336,12 @@
mRotation = rotation;
mDensity = density;
mUiMode = uiMode;
+ mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ configuration);
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
updateDividerConfig(mContext);
- initDividerPosition(mTempRect);
+ initDividerPosition(mTempRect, wasLeftRightSplit);
updateInvisibleRect();
return true;
@@ -338,23 +360,32 @@
}
// We only need new bounds here, other configuration should be update later.
+ final boolean wasLeftRightSplit = SplitScreenUtils.isLeftRightSplit(
+ mAllowLeftRightSplitInPortrait, mIsLargeScreen,
+ mRootBounds.width() >= mRootBounds.height());
mTempRect.set(mRootBounds);
mRootBounds.set(tmpRect);
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
- initDividerPosition(mTempRect);
+ initDividerPosition(mTempRect, wasLeftRightSplit);
}
- private void initDividerPosition(Rect oldBounds) {
- final float snapRatio = (float) mDividePosition
- / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
+ /**
+ * Updates the divider position to the position in the current orientation and bounds using the
+ * snap fraction calculated based on the previous orientation and bounds.
+ */
+ private void initDividerPosition(Rect oldBounds, boolean wasLeftRightSplit) {
+ final float snapRatio = (float) mDividerPosition
+ / (float) (wasLeftRightSplit ? oldBounds.width() : oldBounds.height());
// Estimate position by previous ratio.
final float length =
- (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
+ (float) (mIsLeftRightSplit ? mRootBounds.width() : mRootBounds.height());
final int estimatePosition = (int) (length * snapRatio);
// Init divider position by estimated position using current bounds snap algorithm.
- mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
+ mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
estimatePosition).position;
- updateBounds(mDividePosition);
+ updateBounds(mDividerPosition);
}
private void updateBounds(int position) {
@@ -367,8 +398,7 @@
dividerBounds.set(mRootBounds);
bounds1.set(mRootBounds);
bounds2.set(mRootBounds);
- final boolean isLandscape = isLandscape(mRootBounds);
- if (isLandscape) {
+ if (mIsLeftRightSplit) {
position += mRootBounds.left;
dividerBounds.left = position - mDividerInsets;
dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
@@ -384,7 +414,7 @@
DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
if (setEffectBounds) {
- mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
+ mSurfaceEffectPolicy.applyDividerPosition(position, mIsLeftRightSplit);
}
}
@@ -392,7 +422,7 @@
public void init() {
if (mInitialized) return;
mInitialized = true;
- mSplitWindowManager.init(this, mInsetsState);
+ mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */);
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
@@ -414,14 +444,19 @@
}
/** Releases and re-inflates {@link DividerView} on the root surface. */
- public void update(SurfaceControl.Transaction t) {
+ public void update(SurfaceControl.Transaction t, boolean resetImePosition) {
if (!mInitialized) {
init();
return;
}
mSplitWindowManager.release(t);
- mImePositionProcessor.reset();
- mSplitWindowManager.init(this, mInsetsState);
+ if (resetImePosition) {
+ mImePositionProcessor.reset();
+ }
+ mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */);
+ // Update the surface positions again after recreating the divider in case nothing else
+ // triggers it
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -468,27 +503,29 @@
}
void setDividePosition(int position, boolean applyLayoutChange) {
- mDividePosition = position;
- updateBounds(mDividePosition);
+ mDividerPosition = position;
+ updateBounds(mDividerPosition);
if (applyLayoutChange) {
mSplitLayoutHandler.onLayoutSizeChanged(this);
}
}
/** Updates divide position and split bounds base on the ratio within root bounds. */
- public void setDivideRatio(float ratio) {
- final int position = isLandscape()
- ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
- : mRootBounds.top + (int) (mRootBounds.height() * ratio);
- final DividerSnapAlgorithm.SnapTarget snapTarget =
- mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
+ public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
+ final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
+ snapPosition);
+
+ if (snapTarget == null) {
+ throw new IllegalArgumentException("No SnapTarget for position " + snapPosition);
+ }
+
setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
/** Resets divider position. */
public void resetDividerPosition() {
- mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
- updateBounds(mDividePosition);
+ mDividerPosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividerPosition);
mWinToken1 = null;
mWinToken2 = null;
mWinBounds1.setEmpty();
@@ -511,13 +548,13 @@
* target indicates dismissing split.
*/
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
- switch (snapTarget.flag) {
- case FLAG_DISMISS_START:
+ switch (snapTarget.snapPosition) {
+ case SNAP_TO_START_AND_DISMISS:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
- case FLAG_DISMISS_END:
+ case SNAP_TO_END_AND_DISMISS:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
@@ -552,13 +589,12 @@
}
private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
- final boolean isLandscape = isLandscape(rootBounds);
final Rect insets = getDisplayStableInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
// have difference for avoiding size-compat mode when switching unresizable apps in
// landscape while they are letterboxed.
- if (!isLandscape) {
+ if (!mIsLeftRightSplit) {
final int largerInsets = Math.max(insets.top, insets.bottom);
insets.set(insets.left, largerInsets, insets.right, largerInsets);
}
@@ -568,9 +604,9 @@
rootBounds.width(),
rootBounds.height(),
mDividerSize,
- !isLandscape,
+ !mIsLeftRightSplit,
insets,
- isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
+ mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
/** Fling divider from current position to end or start position then exit */
@@ -632,13 +668,12 @@
/** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
- final boolean isLandscape = isLandscape();
final Rect insets = getDisplayStableInsets(mContext);
- insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
- isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
+ insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
+ mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
- isLandscape ? mBounds2.width() : mBounds2.height()).position;
+ mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
final Rect distBounds1 = new Rect();
final Rect distBounds2 = new Rect();
final Rect distDividerBounds = new Rect();
@@ -669,8 +704,8 @@
@Override
public void onAnimationEnd(Animator animation) {
- mDividePosition = dividerPos;
- updateBounds(mDividePosition);
+ mDividerPosition = dividerPos;
+ updateBounds(mDividerPosition);
finishCallback.accept(insets);
InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@@ -729,15 +764,12 @@
.toRect();
}
- private static boolean isLandscape(Rect bounds) {
- return bounds.width() > bounds.height();
- }
-
/**
- * Return if this layout is landscape.
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
*/
- public boolean isLandscape() {
- return isLandscape(mRootBounds);
+ public boolean isLeftRightSplit() {
+ return mIsLeftRightSplit;
}
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
@@ -839,9 +871,16 @@
/** Dumps the current split bounds recorded in this layout. */
public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + "bounds1=" + mBounds1.toShortString());
- pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString());
- pw.println(prefix + "bounds2=" + mBounds2.toShortString());
+ final String innerPrefix = prefix + "\t";
+ pw.println(prefix + TAG + ":");
+ pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+ pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow);
+ pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide);
+ pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition);
+ pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
+ pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
+ pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
}
/** Handles layout change event. */
@@ -926,32 +965,32 @@
* Applies a parallax to the task to hint dismissing progress.
*
* @param position the split position to apply dismissing parallax effect
- * @param isLandscape indicates whether it's splitting horizontally or vertically
+ * @param isLeftRightSplit indicates whether it's splitting horizontally or vertically
*/
- void applyDividerPosition(int position, boolean isLandscape) {
+ void applyDividerPosition(int position, boolean isLeftRightSplit) {
mDismissingSide = DOCKED_INVALID;
mParallaxOffset.set(0, 0);
mDismissingDimValue = 0;
int totalDismissingDistance = 0;
if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
- mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
- mDividerSnapAlgorithm.getFirstSplitTarget().position;
} else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
- mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
- mDividerSnapAlgorithm.getDismissEndTarget().position;
}
- final boolean topLeftShrink = isLandscape
+ final boolean topLeftShrink = isLeftRightSplit
? position < mWinBounds1.right : position < mWinBounds1.bottom;
if (topLeftShrink) {
- mShrinkSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
mContentBounds.set(mWinBounds1);
mSurfaceBounds.set(mBounds1);
} else {
- mShrinkSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
mContentBounds.set(mWinBounds2);
mSurfaceBounds.set(mBounds2);
}
@@ -962,7 +1001,7 @@
mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
if (mParallaxType == PARALLAX_DISMISSING) {
fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
- if (isLandscape) {
+ if (isLeftRightSplit) {
mParallaxOffset.x = (int) (fraction * totalDismissingDistance);
} else {
mParallaxOffset.y = (int) (fraction * totalDismissingDistance);
@@ -971,7 +1010,7 @@
}
if (mParallaxType == PARALLAX_ALIGN_CENTER) {
- if (isLandscape) {
+ if (isLeftRightSplit) {
mParallaxOffset.x =
(mSurfaceBounds.width() - mContentBounds.width()) / 2;
} else {
@@ -1118,18 +1157,20 @@
// Calculate target bounds offset for IME
mLastYOffset = mYOffsetForIme;
final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- && !isFloating && !isLandscape(mRootBounds) && mImeShown;
+ && !isFloating && !mIsLeftRightSplit && mImeShown;
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
if (mTargetYOffset != mLastYOffset) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Split IME animation starting, fromY=%d toY=%d",
+ mLastYOffset, mTargetYOffset);
// Freeze the configuration size with offset to prevent app get a configuration
// changed or relaunch. This is required to make sure client apps will calculate
// insets properly after layout shifted.
if (mTargetYOffset == 0) {
mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
} else {
- mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
- SplitLayout.this);
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
}
}
@@ -1154,6 +1195,8 @@
public void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Split IME animation ending, canceled=%b", cancel);
onProgress(1.0f);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index be1b9b1..49db8d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -26,6 +26,17 @@
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
+ /** Duration used for every split fade-in or fade-out. */
+ public static final int FADE_DURATION = 133;
+
+ /** Key for passing in widget intents when invoking split from launcher workspace. */
+ public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
+
+ ///////////////
+ // IMPORTANT for the following SPLIT_POSITION and SNAP_TO constants:
+ // These int values must not be changed -- they are persisted to user-defined app pairs, and
+ // will break things if changed.
+ //
/**
* Split position isn't specified normally meaning to use what ever it is currently set to.
@@ -44,11 +55,6 @@
*/
public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1;
- /**
- * Duration used for every split fade-in or fade-out.
- */
- public static final int FADE_DURATION = 133;
-
@IntDef(prefix = {"SPLIT_POSITION_"}, value = {
SPLIT_POSITION_UNDEFINED,
SPLIT_POSITION_TOP_OR_LEFT,
@@ -57,6 +63,61 @@
public @interface SplitPosition {
}
+ /** A snap target in the first half of the screen, where the split is roughly 30-70. */
+ public static final int SNAP_TO_30_70 = 0;
+
+ /** The 50-50 snap target */
+ public static final int SNAP_TO_50_50 = 1;
+
+ /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
+ public static final int SNAP_TO_70_30 = 2;
+
+ /**
+ * These snap targets are used for split pairs in a stable, non-transient state. They may be
+ * persisted in Launcher when the user saves an app pair. They are a subset of
+ * {@link SnapPosition}.
+ */
+ @IntDef(prefix = { "SNAP_TO_" }, value = {
+ SNAP_TO_30_70,
+ SNAP_TO_50_50,
+ SNAP_TO_70_30
+ })
+ public @interface PersistentSnapPosition {}
+
+ /**
+ * Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
+ */
+ public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
+ return snapPosition == SNAP_TO_30_70
+ || snapPosition == SNAP_TO_50_50
+ || snapPosition == SNAP_TO_70_30;
+ }
+
+ /** The divider doesn't snap to any target and is freely placeable. */
+ public static final int SNAP_TO_NONE = 10;
+
+ /** If the divider reaches this value, the left/top task should be dismissed. */
+ public static final int SNAP_TO_START_AND_DISMISS = 11;
+
+ /** If the divider reaches this value, the right/bottom task should be dismissed. */
+ public static final int SNAP_TO_END_AND_DISMISS = 12;
+
+ /** A snap target positioned near the screen edge for a minimized task */
+ public static final int SNAP_TO_MINIMIZE = 13;
+
+ @IntDef(prefix = { "SNAP_TO_" }, value = {
+ SNAP_TO_30_70,
+ SNAP_TO_50_50,
+ SNAP_TO_70_30,
+ SNAP_TO_NONE,
+ SNAP_TO_START_AND_DISMISS,
+ SNAP_TO_END_AND_DISMISS,
+ SNAP_TO_MINIMIZE
+ })
+ public @interface SnapPosition {}
+
+ ///////////////
+
public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index d7ea1c0..0693543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.common.split;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -25,9 +27,14 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
/** Helper utility class for split screen components to use. */
@@ -94,4 +101,38 @@
public static String splitFailureMessage(String caller, String reason) {
return "(" + caller + ") Splitscreen aborted: " + reason;
}
+
+ /**
+ * Returns whether left/right split is allowed in portrait.
+ */
+ public static boolean allowLeftRightSplitInPortrait(Resources res) {
+ return Flags.enableLeftRightSplitInPortrait() && res.getBoolean(
+ com.android.internal.R.bool.config_leftRightSplitInPortrait);
+ }
+
+ /**
+ * Returns whether left/right split is supported in the given configuration.
+ */
+ public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+ Configuration config) {
+ // Compare the max bounds sizes as on near-square devices, the insets may result in a
+ // configuration in the other orientation
+ final boolean isLargeScreen = config.smallestScreenWidthDp >= 600;
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+ final boolean isLandscape = maxBounds.width() >= maxBounds.height();
+ return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape);
+ }
+
+ /**
+ * Returns whether left/right split is supported in the given configuration state. This method
+ * is useful for cases where we need to calculate this given last saved state.
+ */
+ public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+ boolean isLargeScreen, boolean isLandscape) {
+ if (allowLeftRightSplitInPortrait && isLargeScreen) {
+ return !isLandscape;
+ } else {
+ return isLandscape;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 00361d9..8fb9bda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -62,6 +62,10 @@
// Used to "pass" a transaction to WWM.remove so that view removal can be synchronized.
private SurfaceControl.Transaction mSyncTransaction = null;
+ // For saving/restoring state
+ private boolean mLastDividerInteractive = true;
+ private boolean mLastDividerHandleHidden;
+
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
void onLeashReady(SurfaceControl leash);
@@ -107,7 +111,7 @@
}
/** Inflates {@link DividerView} on to the root surface. */
- void init(SplitLayout splitLayout, InsetsState insetsState) {
+ void init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring) {
if (mDividerView != null || mViewHost != null) {
throw new UnsupportedOperationException(
"Try to inflate divider view again without release first");
@@ -130,6 +134,10 @@
lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider);
mViewHost.setView(mDividerView, lp);
mDividerView.setup(splitLayout, this, mViewHost, insetsState);
+ if (isRestoring) {
+ mDividerView.setInteractive(mLastDividerInteractive, mLastDividerHandleHidden,
+ "restore_setup");
+ }
}
/**
@@ -138,6 +146,8 @@
*/
void release(@Nullable SurfaceControl.Transaction t) {
if (mDividerView != null) {
+ mLastDividerInteractive = mDividerView.isInteractive();
+ mLastDividerHandleHidden = mDividerView.isHandleHidden();
mDividerView = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 953efa7..86571cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -20,8 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
-import android.app.TaskInfo.CameraCompatControlState;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -239,7 +239,7 @@
*/
public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
- if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
+ if (taskInfo != null && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
@@ -267,7 +267,7 @@
}
return;
}
- if (!taskInfo.isFromLetterboxDoubleTap) {
+ if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) {
createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
}
}
@@ -348,7 +348,8 @@
// as they are still relevant. Else, if the activity is visible and focused (the one the
// user can see and is using), the user aspect ratio button can potentially be displayed so
// start tracking the buttons visibility for this task.
- if (mTopActivityTaskId != taskInfo.taskId && !taskInfo.isTopActivityTransparent
+ if (mTopActivityTaskId != taskInfo.taskId
+ && !taskInfo.isTopActivityTransparent
&& taskInfo.isVisible && taskInfo.isFocused) {
mTopActivityTaskId = taskInfo.taskId;
setHasShownUserAspectRatioSettingsButton(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index d44b4d8..a0986fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -16,9 +16,10 @@
package com.android.wm.shell.compatui;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+
import android.annotation.IdRes;
-import android.app.TaskInfo;
-import android.app.TaskInfo.CameraCompatControlState;
+import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
@@ -57,10 +58,10 @@
}
void updateCameraTreatmentButton(@CameraCompatControlState int newState) {
- int buttonBkgId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+ int buttonBkgId = newState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
? R.drawable.camera_compat_treatment_suggested_ripple
: R.drawable.camera_compat_treatment_applied_ripple;
- int hintStringId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+ int hintStringId = newState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
? R.string.camera_compat_treatment_suggested_button_description
: R.string.camera_compat_treatment_applied_button_description;
final ImageButton button = findViewById(R.id.camera_compat_treatment_button);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index ce3c509..00e0cdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -16,15 +16,15 @@
package com.android.wm.shell.compatui;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
-import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
@@ -75,8 +75,8 @@
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.topActivityInSizeCompat;
- mCameraCompatControlState = taskInfo.cameraCompatControlState;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
mOnRestartButtonClicked = onRestartButtonClicked;
@@ -127,8 +127,8 @@
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
- mHasSizeCompat = taskInfo.topActivityInSizeCompat;
- mCameraCompatControlState = taskInfo.cameraCompatControlState;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index fce1a39..623fead 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -103,7 +103,8 @@
R.dimen.letterbox_education_dialog_margin);
mDockStateReader = dockStateReader;
mCompatUIConfiguration = compatUIConfiguration;
- mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
+ mEligibleForLetterboxEducation =
+ taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation;
}
@Override
@@ -204,7 +205,8 @@
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
- mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
+ mEligibleForLetterboxEducation =
+ taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation;
return super.updateCompatInfo(taskInfo, taskListener, canShow);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 5612bc8..835f1af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -21,6 +21,7 @@
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Rect;
@@ -89,11 +90,12 @@
BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback,
Function<Integer, Integer> disappearTimeSupplier) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
- mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
- mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
- mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
- mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
- mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ mIsActivityLetterboxed = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
+ mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
+ mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
+ mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
+ mTopActivityLetterboxHeight = appCompatTaskInfo.topActivityLetterboxHeight;
mCompatUIConfiguration = compatUIConfiguration;
mMainExecutor = mainExecutor;
mOnDismissCallback = onDismissCallback;
@@ -145,12 +147,13 @@
final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition;
final int prevTopActivityLetterboxWidth = mTopActivityLetterboxWidth;
final int prevTopActivityLetterboxHeight = mTopActivityLetterboxHeight;
- mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
- mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
- mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
- mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
- mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
- mHasUserDoubleTapped = taskInfo.isFromLetterboxDoubleTap;
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ mIsActivityLetterboxed = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
+ mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
+ mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
+ mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
+ mTopActivityLetterboxHeight = appCompatTaskInfo.topActivityLetterboxHeight;
+ mHasUserDoubleTapped = appCompatTaskInfo.isFromLetterboxDoubleTap;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index c2dec62..afd3b14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.LayoutInflater;
@@ -227,9 +228,12 @@
}
private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
- return taskInfo.topActivityEligibleForUserAspectRatioButton
- && (taskInfo.topActivityBoundsLetterboxed
- || taskInfo.isUserFullscreenOverrideEnabled)
+ final Intent intent = taskInfo.baseIntent;
+ return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
+ || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+ && Intent.ACTION_MAIN.equals(intent.getAction())
+ && intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 9facbd5..b52a118 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -49,11 +49,11 @@
import java.util.Optional;
/**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link com.android.systemui.dagger.WMComponent}).
*
- * This module only defines Shell dependencies for the TV SystemUI implementation. Common
+ * <p>This module only defines Shell dependencies for the TV SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = {TvPipModule.class})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 45869e1..3c6bc17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -25,6 +25,7 @@
import android.os.SystemProperties;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.window.SystemPerformanceHinter;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
@@ -38,6 +39,7 @@
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DevicePostureController;
@@ -71,7 +73,6 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -85,6 +86,7 @@
import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.performance.PerfHintController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -101,6 +103,7 @@
import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -118,9 +121,9 @@
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * SysUIComponent, see {@link com.android.systemui.dagger.WMComponent}).
*
- * This module only defines *common* dependencies across various SystemUI implementations,
+ * <p>This module only defines *common* dependencies across various SystemUI implementations,
* dependencies that are device/form factor SystemUI implementation specific should go into their
* respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
@@ -218,34 +221,57 @@
Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- CompatUIController compatUI,
+ Optional<CompatUIController> compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
+ @ShellMainThread ShellExecutor mainExecutor) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTaskOrganizerOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
- return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
- unfoldAnimationController, recentTasksOptional, mainExecutor);
+ return new ShellTaskOrganizer(
+ shellInit,
+ shellCommandHandler,
+ compatUI.orElse(null),
+ unfoldAnimationController,
+ recentTasksOptional,
+ mainExecutor);
}
@WMSingleton
@Provides
- static CompatUIController provideCompatUIController(Context context,
+ static Optional<CompatUIController> provideCompatUIController(
+ Context context,
ShellInit shellInit,
ShellController shellController,
- DisplayController displayController, DisplayInsetsController displayInsetsController,
- DisplayImeController imeController, SyncTransactionQueue syncQueue,
- @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
- CompatUIShellCommandHandler compatUIShellCommandHandler,
- AccessibilityManager accessibilityManager) {
- return new CompatUIController(context, shellInit, shellController, displayController,
- displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
- dockStateReader, compatUIConfiguration, compatUIShellCommandHandler,
- accessibilityManager);
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ DisplayImeController imeController,
+ SyncTransactionQueue syncQueue,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Lazy<Transitions> transitionsLazy,
+ Lazy<DockStateReader> dockStateReader,
+ Lazy<CompatUIConfiguration> compatUIConfiguration,
+ Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
+ Lazy<AccessibilityManager> accessibilityManager) {
+ if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ new CompatUIController(
+ context,
+ shellInit,
+ shellController,
+ displayController,
+ displayInsetsController,
+ imeController,
+ syncQueue,
+ mainExecutor,
+ transitionsLazy,
+ dockStateReader.get(),
+ compatUIConfiguration.get(),
+ compatUIShellCommandHandler.get(),
+ accessibilityManager.get()));
}
@WMSingleton
@@ -296,6 +322,20 @@
return new LaunchAdjacentController(syncQueue);
}
+ @WMSingleton
+ @Provides
+ static Optional<SystemPerformanceHinter> provideSystemPerformanceHinter(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
+ if (!com.android.window.flags.Flags.explicitRefreshRateHints()) {
+ return Optional.empty();
+ }
+ final PerfHintController perfHintController =
+ new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
+ return Optional.of(perfHintController.getHinter());
+ }
+
//
// Back animation
//
@@ -322,16 +362,26 @@
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
- BackAnimationBackground backAnimationBackground
- ) {
+ BackAnimationBackground backAnimationBackground,
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
if (BackAnimationController.IS_ENABLED) {
- return Optional.of(
- new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context, backAnimationBackground));
+ return shellBackAnimationRegistry.map(
+ (animations) ->
+ new BackAnimationController(
+ shellInit,
+ shellController,
+ shellExecutor,
+ backgroundHandler,
+ context,
+ backAnimationBackground,
+ animations));
}
return Optional.empty();
}
+ @BindsOptionalOf
+ abstract ShellBackAnimationRegistry optionalBackAnimationRegistry();
+
//
// PiP (optional feature)
//
@@ -582,6 +632,7 @@
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
@@ -589,15 +640,22 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
- ShellCommandHandler shellCommandHandler,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ HomeTransitionObserver homeTransitionObserver) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
- return new Transitions(context, shellInit, shellController, organizer, pool,
- displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler,
- rootTaskDisplayAreaOrganizer);
+ return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
+ pool, displayController, mainExecutor, mainHandler, animExecutor,
+ rootTaskDisplayAreaOrganizer, homeTransitionObserver);
+ }
+
+ @WMSingleton
+ @Provides
+ static HomeTransitionObserver provideHomeTransitionObserver(Context context,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new HomeTransitionObserver(context, mainExecutor);
}
@WMSingleton
@@ -614,11 +672,12 @@
@Provides
static KeyguardTransitionHandler provideKeyguardTransitionHandler(
ShellInit shellInit,
+ ShellController shellController,
Transitions transitions,
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
return new KeyguardTransitionHandler(
- shellInit, transitions, mainHandler, mainExecutor);
+ shellInit, shellController, transitions, mainHandler, mainExecutor);
}
@WMSingleton
@@ -635,15 +694,15 @@
@WMSingleton
@Provides
static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer(
- @ShellMainThread ShellExecutor mainExecutor, Context context) {
- return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
+ @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) {
+ return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit);
}
@WMSingleton
@Provides
static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
- @ShellMainThread ShellExecutor mainExecutor) {
- return new RootDisplayAreaOrganizer(mainExecutor);
+ @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) {
+ return new RootDisplayAreaOrganizer(mainExecutor, shellInit);
}
@WMSingleton
@@ -789,30 +848,10 @@
@WMSingleton
@Provides
static Optional<DesktopMode> provideDesktopMode(
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController) {
- if (DesktopModeStatus.isProto2Enabled()) {
- return desktopTasksController.map(DesktopTasksController::asDesktopMode);
- }
- return desktopModeController.map(DesktopModeController::asDesktopMode);
+ return desktopTasksController.map(DesktopTasksController::asDesktopMode);
}
- @BindsOptionalOf
- @DynamicOverride
- abstract DesktopModeController optionalDesktopModeController();
-
- @WMSingleton
- @Provides
- static Optional<DesktopModeController> provideDesktopModeController(
- @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
- // Use optional-of-lazy for the dependency that this provider relies on.
- // Lazy ensures that this provider will not be the cause the dependency is created
- // when it will not be returned due to the condition below.
- if (DesktopModeStatus.isProto1Enabled()) {
- return desktopModeController.map(Lazy::get);
- }
- return Optional.empty();
- }
@BindsOptionalOf
@DynamicOverride
@@ -825,10 +864,12 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isProto2Enabled()) {
- return desktopTasksController.map(Lazy::get);
- }
- return Optional.empty();
+ return desktopTasksController.flatMap((lazy)-> {
+ if (DesktopModeStatus.isEnabled()) {
+ return Optional.of(lazy.get());
+ }
+ return Optional.empty();
+ });
}
@BindsOptionalOf
@@ -842,10 +883,12 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isAnyEnabled()) {
- return desktopModeTaskRepository.map(Lazy::get);
- }
- return Optional.empty();
+ return desktopModeTaskRepository.flatMap((lazy)-> {
+ if (DesktopModeStatus.isEnabled()) {
+ return Optional.of(lazy.get());
+ }
+ return Optional.empty();
+ });
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 065e7b0..71bf487 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,9 +32,11 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
@@ -52,11 +54,12 @@
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
@@ -76,6 +79,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -100,17 +104,19 @@
import java.util.Optional;
/**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link WMComponent}).
*
- * This module only defines Shell dependencies for handheld SystemUI implementation. Common
+ * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
-@Module(includes = {
- WMShellBaseModule.class,
- PipModule.class
-})
+@Module(
+ includes = {
+ WMShellBaseModule.class,
+ PipModule.class,
+ ShellBackAnimationModule.class,
+ })
public abstract class WMShellModule {
//
@@ -132,11 +138,18 @@
@WMSingleton
@Provides
+ static BubbleEducationController provideBubbleEducationProvider(Context context) {
+ return new BubbleEducationController(context);
+ }
+
+ @WMSingleton
+ @Provides
static BubbleData provideBubbleData(Context context,
BubbleLogger logger,
BubblePositioner positioner,
+ BubbleEducationController educationController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new BubbleData(context, logger, positioner, mainExecutor);
+ return new BubbleData(context, logger, positioner, educationController, mainExecutor);
}
// Note: Handler needed for LauncherApps.register
@@ -164,6 +177,7 @@
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
@@ -173,7 +187,8 @@
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
+ taskViewTransitions, transitions, syncQueue, wmService,
+ ProdBubbleProperties.INSTANCE);
}
//
@@ -187,26 +202,32 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
- Optional<DesktopTasksController> desktopTasksController) {
- if (DesktopModeStatus.isAnyEnabled()) {
+ Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
shellInit,
+ shellCommandHandler,
taskOrganizer,
displayController,
shellController,
+ displayInsetsController,
syncQueue,
transitions,
- desktopModeController,
- desktopTasksController);
+ desktopTasksController,
+ recentsTransitionHandler,
+ rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
context,
@@ -346,14 +367,14 @@
@Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
KeyguardTransitionHandler keyguardTransitionHandler,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
pipTransitionController, recentsTransitionHandler,
- keyguardTransitionHandler, desktopModeController, desktopTasksController,
- unfoldHandler);
+ keyguardTransitionHandler, desktopTasksController,
+ unfoldHandler, activityEmbeddingController);
}
@WMSingleton
@@ -361,9 +382,10 @@
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- Optional<RecentTasksController> recentTasksController) {
+ Optional<RecentTasksController> recentTasksController,
+ HomeTransitionObserver homeTransitionObserver) {
return new RecentsTransitionHandler(shellInit, transitions,
- recentTasksController.orElse(null));
+ recentTasksController.orElse(null), homeTransitionObserver);
}
//
@@ -464,24 +486,6 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
- @ShellMainThread Handler mainHandler,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
- return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
- mainExecutor);
- }
-
- @WMSingleton
- @Provides
- @DynamicOverride
static DesktopTasksController provideDesktopTasksController(
Context context,
ShellInit shellInit,
@@ -495,15 +499,28 @@
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
+ DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
+ RecentsTransitionHandler recentsTransitionHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
- launchAdjacentController, mainExecutor);
+ toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler,
+ desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler,
+ mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static DragToDesktopTransitionHandler provideDragToDesktopTransitionHandler(
+ Context context,
+ Transitions transitions,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ return new DragToDesktopTransitionHandler(context, transitions,
+ rootTaskDisplayAreaOrganizer);
}
@WMSingleton
@@ -546,8 +563,7 @@
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
- DefaultMixedHandler defaultMixedHandler,
- Optional<DesktopModeController> desktopModeController) {
+ DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
new file mode 100644
index 0000000..795bc1a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.dagger.back;
+
+import com.android.wm.shell.back.CrossActivityBackAnimation;
+import com.android.wm.shell.back.CrossTaskBackAnimation;
+import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.ShellBackAnimation;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/** Default animation definitions for predictive back. */
+@Module
+public interface ShellBackAnimationModule {
+ /** Default animation registry */
+ @Provides
+ static ShellBackAnimationRegistry provideBackAnimationRegistry(
+ @ShellBackAnimation.CrossActivity ShellBackAnimation crossActivity,
+ @ShellBackAnimation.CrossTask ShellBackAnimation crossTask,
+ @ShellBackAnimation.CustomizeActivity ShellBackAnimation customizeActivity) {
+ return new ShellBackAnimationRegistry(
+ crossActivity,
+ crossTask,
+ /* dialogCloseAnimation */ null,
+ customizeActivity,
+ /* defaultBackToHomeAnimation= */ null);
+ }
+
+ /** Default cross activity back animation */
+ @Binds
+ @ShellBackAnimation.CrossActivity
+ ShellBackAnimation bindCrossActivityShellBackAnimation(
+ CrossActivityBackAnimation crossActivityBackAnimation);
+
+ /** Default cross task back animation */
+ @Binds
+ @ShellBackAnimation.CrossTask
+ ShellBackAnimation provideCrossTaskShellBackAnimation(
+ CrossTaskBackAnimation crossTaskBackAnimation);
+
+ /** Default customized activity back animation */
+ @Binds
+ @ShellBackAnimation.CustomizeActivity
+ ShellBackAnimation provideCustomizeActivityShellBackAnimation(
+ CustomizeActivityAnimation customizeActivityAnimation);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 0000000..74a29dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index af97cf6..3b48c67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,31 @@
package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
+import android.content.Context;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
-import com.android.wm.shell.pip2.PipTransition;
+import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
* the successor of its sibling {@link Pip1Module}.
@@ -42,8 +54,35 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm) {
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ Optional<PipController> pipController,
+ @NonNull PipScheduler pipScheduler) {
return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
- pipBoundsAlgorithm);
+ pipBoundsAlgorithm, pipScheduler);
+ }
+
+ @WMSingleton
+ @Provides
+ static Optional<PipController> providePipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellController, displayController, displayInsetsController,
+ pipDisplayLayoutState));
+ }
+ }
+
+ @WMSingleton
+ @Provides
+ static PipScheduler providePipScheduler(Context context,
+ PipBoundsState pipBoundsState,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 570f0a3..f2631ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -19,6 +19,7 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.PipTransition;
import dagger.Module;
import dagger.Provides;
@@ -36,7 +37,7 @@
@Provides
static PipTransitionController providePipTransitionController(
com.android.wm.shell.pip.PipTransition legacyPipTransition,
- com.android.wm.shell.pip2.PipTransition newPipTransition) {
+ PipTransition newPipTransition) {
if (PipUtils.isPip2ExperimentEnabled()) {
return newPipTransition;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index a9675f9..1947097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -20,6 +20,8 @@
import android.os.Handler;
import android.os.SystemClock;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -41,7 +43,6 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
@@ -78,11 +79,12 @@
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
+ PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
- PipTransitionController pipTransitionController,
+ TvPipTransition tvPipTransition,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
@@ -99,9 +101,10 @@
pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
+ pipTransitionState,
pipAppOpsListener,
pipTaskOrganizer,
- pipTransitionController,
+ tvPipTransition,
tvPipMenuController,
pipMediaController,
tvPipNotificationController,
@@ -151,25 +154,23 @@
return new LegacySizeSpecSource(context, pipDisplayLayoutState);
}
- // Handler needed for loadDrawableAsync() in PipControlsViewController
@WMSingleton
@Provides
- static PipTransitionController provideTvPipTransition(
+ static TvPipTransition provideTvPipTransition(
Context context,
- ShellInit shellInit,
- ShellTaskOrganizer shellTaskOrganizer,
- Transitions transitions,
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
TvPipBoundsState tvPipBoundsState,
- PipDisplayLayoutState pipDisplayLayoutState,
- PipTransitionState pipTransitionState,
- TvPipMenuController pipMenuController,
+ TvPipMenuController tvPipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipTransitionState pipTransitionState,
PipAnimationController pipAnimationController,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ PipDisplayLayoutState pipDisplayLayoutState) {
return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
- tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
- tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
- Optional.empty());
+ tvPipBoundsState, tvPipMenuController, tvPipBoundsAlgorithm, pipTransitionState,
+ pipAnimationController, pipSurfaceTransactionHelper, pipDisplayLayoutState);
}
@WMSingleton
@@ -207,7 +208,7 @@
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
- PipTransitionController pipTransitionController,
+ TvPipTransition tvPipTransition,
PipParamsChangedForwarder pipParamsChangedForwarder,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
@@ -217,7 +218,7 @@
return new TvPipTaskOrganizer(context,
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
- pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
shellTaskOrganizer, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
deleted file mode 100644
index 5b24d7a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * 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.desktopmode;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.ContentObserver;
-import android.graphics.Region;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Handles windowing changes when desktop mode system setting changes
- */
-public class DesktopModeController implements RemoteCallable<DesktopModeController>,
- Transitions.TransitionHandler {
-
- private final Context mContext;
- private final ShellController mShellController;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
- private final Transitions mTransitions;
- private final DesktopModeTaskRepository mDesktopModeTaskRepository;
- private final ShellExecutor mMainExecutor;
- private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
- private final SettingsObserver mSettingsObserver;
-
- public DesktopModeController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Transitions transitions,
- DesktopModeTaskRepository desktopModeTaskRepository,
- @ShellMainThread Handler mainHandler,
- @ShellMainThread ShellExecutor mainExecutor) {
- mContext = context;
- mShellController = shellController;
- mShellTaskOrganizer = shellTaskOrganizer;
- mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
- mTransitions = transitions;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
- mMainExecutor = mainExecutor;
- mSettingsObserver = new SettingsObserver(mContext, mainHandler);
- if (DesktopModeStatus.isProto1Enabled()) {
- shellInit.addInitCallback(this::onInit, this);
- }
- }
-
- private void onInit() {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
- this::createExternalInterface, this);
- mSettingsObserver.observe();
- if (DesktopModeStatus.isActive(mContext)) {
- updateDesktopModeActive(true);
- }
- mTransitions.addHandler(this);
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- /**
- * Get connection interface between sysui and shell
- */
- public DesktopMode asDesktopMode() {
- return mDesktopModeImpl;
- }
-
- /**
- * Creates a new instance of the external interface to pass to another process.
- */
- private ExternalInterfaceBinder createExternalInterface() {
- return new IDesktopModeImpl(this);
- }
-
- /**
- * Adds a listener to find out about changes in the visibility of freeform tasks.
- *
- * @param listener the listener to add.
- * @param callbackExecutor the executor to call the listener on.
- */
- public void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
- Executor callbackExecutor) {
- mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
- }
-
- /**
- * Adds a listener to track changes to corners of desktop mode tasks.
- * @param listener the listener to add.
- * @param callbackExecutor the executor to call the listener on.
- */
- public void addTaskCornerListener(Consumer<Region> listener,
- Executor callbackExecutor) {
- mDesktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor);
- }
-
- @VisibleForTesting
- void updateDesktopModeActive(boolean active) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
-
- int displayId = mContext.getDisplayId();
-
- ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level so tasks inherit it
- clearFreeformForStandardTasks(runningTasks, wct);
- if (active) {
- moveHomeBehindVisibleTasks(runningTasks, wct);
- setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
- } else {
- clearBoundsForStandardTasks(runningTasks, wct);
- setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
- } else {
- mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
- }
- }
-
- private WindowContainerTransaction clearBoundsForStandardTasks(
- ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
- for (RunningTaskInfo taskInfo : runningTasks) {
- if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
- WindowContainerTransaction wct) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
- for (RunningTaskInfo taskInfo : runningTasks) {
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- }
-
- private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
- WindowContainerTransaction wct) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
- RunningTaskInfo homeTask = null;
- ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
- for (RunningTaskInfo taskInfo : runningTasks) {
- if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
- homeTask = taskInfo;
- } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && taskInfo.isVisible()) {
- visibleTasks.add(taskInfo);
- }
- }
- if (homeTask == null) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
- } else {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
- visibleTasks.size());
- wct.reorder(homeTask.getToken(), true /* onTop */);
- for (RunningTaskInfo task : visibleTasks) {
- wct.reorder(task.getToken(), true /* onTop */);
- }
- }
- }
-
- private void setDisplayAreaWindowingMode(int displayId,
- @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- displayId);
- if (displayAreaInfo == null) {
- ProtoLog.e(WM_SHELL_DESKTOP_MODE,
- "unable to update windowing mode for display %d display not found", displayId);
- return;
- }
-
- ProtoLog.v(WM_SHELL_DESKTOP_MODE,
- "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
- displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
- windowingMode);
-
- wct.setWindowingMode(displayAreaInfo.token, windowingMode);
- }
-
- /**
- * Show apps on desktop
- */
- void showDesktopApps(int displayId) {
- // Bring apps to front, ignoring their visibility status to always ensure they are on top.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- bringDesktopAppsToFront(displayId, wct);
-
- if (!wct.isEmpty()) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/268662477): add animation for the transition
- mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
- }
- }
- }
-
- /** Get number of tasks that are marked as visible */
- int getVisibleTaskCount(int displayId) {
- return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
- }
-
- private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
- final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-
- final List<RunningTaskInfo> taskInfos = new ArrayList<>();
- for (Integer taskId : activeTasks) {
- RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
- if (taskInfo != null) {
- taskInfos.add(taskInfo);
- }
- }
-
- if (taskInfos.isEmpty()) {
- return;
- }
-
- moveHomeTaskToFront(wct);
-
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: reordering all active tasks to the front");
- final List<Integer> allTasksInZOrder =
- mDesktopModeTaskRepository.getFreeformTasksInZOrder();
- // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
- // in the WCT.
- taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
- for (RunningTaskInfo task : taskInfos) {
- wct.reorder(task.token, true);
- }
- }
-
- private void moveHomeTaskToFront(WindowContainerTransaction wct) {
- for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
- if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
- wct.reorder(task.token, true /* onTop */);
- return;
- }
- }
- }
-
- /**
- * Update corner rects stored for a specific task
- * @param taskId task to update
- * @param taskCorners task's new corner handles
- */
- public void onTaskCornersChanged(int taskId, Region taskCorners) {
- mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners);
- }
-
- /**
- * Remove corners saved for a task. Likely used due to task closure.
- * @param taskId task to remove
- */
- public void removeCornersForTask(int taskId) {
- mDesktopModeTaskRepository.removeTaskCorners(taskId);
- }
-
- /**
- * Moves a specifc task to the front.
- * @param taskInfo the task to show in front.
- */
- public void moveTaskToFront(RunningTaskInfo taskInfo) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(taskInfo.token, true /* onTop */);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
- }
- }
-
- /**
- * Turn desktop mode on or off
- * @param active the desired state for desktop mode setting
- */
- public void setDesktopModeActive(boolean active) {
- int value = active ? 1 : 0;
- Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
- }
-
- /**
- * Returns the windowing mode of the display area with the specified displayId.
- * @param displayId
- * @return
- */
- public int getDisplayAreaWindowingMode(int displayId) {
- return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
- .configuration.windowConfiguration.getWindowingMode();
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- // This handler should never be the sole handler, so should not animate anything.
- return false;
- }
-
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- RunningTaskInfo triggerTask = request.getTriggerTask();
- // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
- // freeform
- if (!DesktopModeStatus.isActive(mContext)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "skip shell transition request: desktop mode not active");
- return null;
- }
- if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "skip shell transition request: unsupported type %s",
- WindowManager.transitTypeToString(request.getType()));
- return null;
- }
- if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
- return null;
- }
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- bringDesktopAppsToFront(triggerTask.displayId, wct);
- wct.reorder(triggerTask.token, true /* onTop */);
-
- return wct;
- }
-
- /**
- * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
- * This is intended to be used when desktop mode is part of another animation but isn't, itself,
- * animating.
- */
- public void syncSurfaceState(@NonNull TransitionInfo info,
- SurfaceControl.Transaction finishTransaction) {
- // Add rounded corners to freeform windows
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.dialogCornerRadius});
- final int cornerRadius = ta.getDimensionPixelSize(0, 0);
- ta.recycle();
- for (TransitionInfo.Change change: info.getChanges()) {
- if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
- }
- }
- }
-
- /**
- * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
- */
- private final class SettingsObserver extends ContentObserver {
-
- private final Uri mDesktopModeSetting = Settings.System.getUriFor(
- Settings.System.DESKTOP_MODE);
-
- private final Context mContext;
-
- SettingsObserver(Context context, Handler handler) {
- super(handler);
- mContext = context;
- }
-
- public void observe() {
- // TODO(b/242867463): listen for setting change for all users
- mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
- false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
- }
-
- @Override
- public void onChange(boolean selfChange, @Nullable Uri uri) {
- if (mDesktopModeSetting.equals(uri)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
- desktopModeSettingChanged();
- }
- }
-
- private void desktopModeSettingChanged() {
- boolean enabled = DesktopModeStatus.isActive(mContext);
- updateDesktopModeActive(enabled);
- }
- }
-
- /**
- * The interface for calls from outside the shell, within the host process.
- */
- @ExternalThread
- private final class DesktopModeImpl implements DesktopMode {
-
- @Override
- public void addVisibleTasksListener(
- DesktopModeTaskRepository.VisibleTasksListener listener,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor);
- });
- }
-
- @Override
- public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor);
- });
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub
- implements ExternalInterfaceBinder {
-
- private DesktopModeController mController;
-
- IDesktopModeImpl(DesktopModeController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- @Override
- public void invalidate() {
- mController = null;
- }
-
- @Override
- public void showDesktopApps(int displayId) {
- executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
- controller -> controller.showDesktopApps(displayId));
- }
-
- @Override
- public void showDesktopApp(int taskId) throws RemoteException {
- // TODO
- }
-
- @Override
- public int getVisibleTaskCount(int displayId) throws RemoteException {
- int[] result = new int[1];
- executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
- controller -> result[0] = controller.getVisibleTaskCount(displayId),
- true /* blocking */
- );
- return result[0];
- }
-
- @Override
- public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
-
- }
-
- @Override
- public void stashDesktopApps(int displayId) throws RemoteException {
- // Stashing of desktop apps not needed. Apps always launch on desktop
- }
-
- @Override
- public void hideStashedDesktopApps(int displayId) throws RemoteException {
- // Stashing of desktop apps not needed. Apps always launch on desktop
- }
-
- @Override
- public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
- // TODO(b/261234402): move visibility from sysui state to listener
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 517f9f2..dc82fc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,28 +16,19 @@
package com.android.wm.shell.desktopmode;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-
-import android.content.Context;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.Flags;
/**
* Constants for desktop mode feature
*/
public class DesktopModeStatus {
- /**
- * Flag to indicate whether desktop mode is available on the device
- */
- private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode", false);
+ private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
/**
- * Flag to indicate whether desktop mode proto 2 is available on the device
+ * Flag to indicate whether desktop mode proto is available on the device
*/
private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_2", false);
@@ -64,28 +55,19 @@
"persist.wm.debug.desktop_stashing", false);
/**
- * Return {@code true} if desktop mode support is enabled
- */
- public static boolean isProto1Enabled() {
- return IS_SUPPORTED;
- }
-
- /**
* Return {@code true} is desktop windowing proto 2 is enabled
*/
- public static boolean isProto2Enabled() {
+ public static boolean isEnabled() {
+ // Check for aconfig flag first
+ if (ENABLE_DESKTOP_WINDOWING) {
+ return true;
+ }
+ // Fall back to sysprop flag
+ // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
return IS_PROTO2_ENABLED;
}
/**
- * Return {@code true} if proto 1 or 2 is enabled.
- * Can be used to guard logic that is common for both prototypes.
- */
- public static boolean isAnyEnabled() {
- return isProto1Enabled() || isProto2Enabled();
- }
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -99,26 +81,4 @@
public static boolean isStashingEnabled() {
return IS_STASHING_ENABLED;
}
- /**
- * Check if desktop mode is active
- *
- * @return {@code true} if active
- */
- public static boolean isActive(Context context) {
- if (!isAnyEnabled()) {
- return false;
- }
- if (isProto2Enabled()) {
- // Desktop mode is always active in prototype 2
- return true;
- }
- try {
- int result = Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- return result != 0;
- } catch (Exception e) {
- ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
- return false;
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c05af73..c0fc02fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -52,8 +52,8 @@
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
// Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
- // Track corners of desktop tasks, used to determine gesture exclusion
- private val desktopCorners = SparseArray<Region>()
+ // Track corner/caption regions of desktop tasks, used to determine gesture exclusion
+ private val desktopExclusionRegions = SparseArray<Region>()
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -96,10 +96,11 @@
}
/**
- * Add a Consumer which will inform other classes of changes to corners for all Desktop tasks.
+ * Add a Consumer which will inform other classes of changes to exclusion regions for all
+ * Desktop tasks.
*/
- fun setTaskCornerListener(cornersListener: Consumer<Region>, executor: Executor) {
- desktopGestureExclusionListener = cornersListener
+ fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
+ desktopGestureExclusionListener = regionListener
desktopGestureExclusionExecutor = executor
executor.execute {
desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
@@ -107,14 +108,14 @@
}
/**
- * Create a new merged region representative of all corners in all desktop tasks.
+ * Create a new merged region representative of all exclusion regions in all desktop tasks.
*/
private fun calculateDesktopExclusionRegion(): Region {
- val desktopCornersRegion = Region()
- desktopCorners.valueIterator().forEach { taskCorners ->
- desktopCornersRegion.op(taskCorners, Region.Op.UNION)
+ val desktopExclusionRegion = Region()
+ desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion ->
+ desktopExclusionRegion.op(taskExclusionRegion, Region.Op.UNION)
}
- return desktopCornersRegion
+ return desktopExclusionRegion
}
/**
@@ -294,22 +295,24 @@
}
/**
- * Updates the active desktop corners; if desktopCorners has been accepted by
- * desktopCornersListener, it will be updated in the appropriate classes.
+ * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been
+ * accepted by desktopGestureExclusionListener, it will be updated in the
+ * appropriate classes.
*/
- fun updateTaskCorners(taskId: Int, taskCorners: Region) {
- desktopCorners.put(taskId, taskCorners)
+ fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) {
+ desktopExclusionRegions.put(taskId, taskExclusionRegions)
desktopGestureExclusionExecutor?.execute {
desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
}
}
/**
- * Removes the active desktop corners for the specified task; if desktopCorners has been
- * accepted by desktopCornersListener, it will be updated in the appropriate classes.
+ * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion
+ * has been accepted by desktopGestureExclusionListener, it will be updated in the
+ * appropriate classes.
*/
- fun removeTaskCorners(taskId: Int) {
- desktopCorners.delete(taskId)
+ fun removeExclusionRegion(taskId: Int) {
+ desktopExclusionRegions.delete(taskId)
desktopGestureExclusionExecutor?.execute {
desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4e418e1..144555d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.desktopmode
-import android.R
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
-import android.content.res.TypedArray
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -40,10 +36,12 @@
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.RemoteTransition
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -60,12 +58,17 @@
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.ShellSharedConstants
+import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
@@ -89,8 +92,10 @@
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler,
+ private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
+ private val recentsTransitionHandler: RecentsTransitionHandler,
@ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
@@ -106,21 +111,37 @@
launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
}
}
+ private val dragToDesktopStateListener = object : DragToDesktopStateListener {
+ override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) {
+ removeVisualIndicator(tx)
+ }
+
+ override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) {
+ removeVisualIndicator(tx)
+ }
+
+ private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
+ visualIndicator?.releaseVisualIndicator(tx)
+ visualIndicator = null
+ }
+ }
private val transitionAreaHeight
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+ )
private val transitionAreaWidth
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width)
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
+ )
- // This is public to avoid cyclic dependency; it is set by SplitScreenController
- lateinit var splitScreenController: SplitScreenController
+ private var recentsAnimationRunning = false
+ private lateinit var splitScreenController: SplitScreenController
init {
desktopMode = DesktopModeImpl()
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
shellInit.addInitCallback({ onInit() }, this)
}
}
@@ -135,23 +156,44 @@
)
transitions.addHandler(this)
desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
+ dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener)
+ recentsTransitionHandler.addTransitionStateListener(
+ object : RecentsTransitionStateListener {
+ override fun onAnimationStateChanged(running: Boolean) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: recents animation state changed running=%b",
+ running
+ )
+ recentsAnimationRunning = running
+ }
+ }
+ )
+ }
+
+ /** Setter needed to avoid cyclic dependency. */
+ fun setSplitScreenController(controller: SplitScreenController) {
+ splitScreenController = controller
+ dragToDesktopTransitionHandler.setSplitScreenController(controller)
}
/** Show all tasks, that are part of the desktop, on top of launcher */
- fun showDesktopApps(displayId: Int) {
+ fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
val wct = WindowContainerTransaction()
- // TODO(b/278084491): pass in display id
bringDesktopAppsToFront(displayId, wct)
- // Execute transaction if there are pending operations
- if (!wct.isEmpty) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/268662477): add animation for the transition
- transitions.startTransition(TRANSIT_NONE, wct, null /* handler */)
- } else {
- shellTaskOrganizer.applyTransaction(wct)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/255649902): ensure remote transition is supplied once state is introduced
+ val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+ val handler = remoteTransition?.let {
+ OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
}
+ transitions.startTransition(transitionType, wct, handler).also { t ->
+ handler?.setTransition(t)
+ }
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
}
}
@@ -212,6 +254,7 @@
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
)
+ exitSplitIfApplicable(wct, task)
// Bring other apps to front first
bringDesktopAppsToFront(task.displayId, wct)
addMoveToDesktopChanges(wct, task)
@@ -224,55 +267,43 @@
}
/**
- * The first part of the animated move to desktop transition. Applies the changes to move task
- * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
- * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
+ * The first part of the animated drag to desktop transition. This is
+ * followed with a call to [finalizeDragToDesktop] or [cancelDragToDesktop].
*/
- fun startMoveToDesktop(
+ fun startDragToDesktop(
taskInfo: RunningTaskInfo,
- startBounds: Rect,
- dragToDesktopValueAnimator: MoveToDesktopAnimator
+ dragToDesktopValueAnimator: MoveToDesktopAnimator,
+ windowDecor: DesktopModeWindowDecoration
) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: startMoveToDesktop taskId=%d",
- taskInfo.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: startDragToDesktop taskId=%d",
+ taskInfo.taskId
)
- val wct = WindowContainerTransaction()
- moveHomeTaskToFront(wct)
- addMoveToDesktopChanges(wct, taskInfo)
- wct.setBounds(taskInfo.token, startBounds)
-
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
- mOnAnimationFinishedCallback)
- } else {
- shellTaskOrganizer.applyTransaction(wct)
- }
+ dragToDesktopTransitionHandler.startDragToDesktopTransition(
+ taskInfo.taskId,
+ dragToDesktopValueAnimator,
+ windowDecor
+ )
}
/**
- * The second part of the animated move to desktop transition, called after
- * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+ * The second part of the animated drag to desktop transition, called after
+ * [startDragToDesktop].
*/
- private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
- taskInfo.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: finalizeDragToDesktop taskId=%d",
+ taskInfo.taskId
)
val wct = WindowContainerTransaction()
+ exitSplitIfApplicable(wct, taskInfo)
+ moveHomeTaskToFront(wct)
bringDesktopAppsToFront(taskInfo.displayId, wct)
addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, freeformBounds)
-
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
- mOnAnimationFinishedCallback)
- } else {
- shellTaskOrganizer.applyTransaction(wct)
- releaseVisualIndicator()
- }
+ dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
}
/**
@@ -289,24 +320,10 @@
}
/** Move a task with given `taskId` to fullscreen */
- fun moveToFullscreen(taskId: Int) {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
- }
-
- /** Move a task to fullscreen */
- fun moveToFullscreen(task: RunningTaskInfo) {
- KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFullscreen taskId=%d",
- task.taskId
- )
-
- val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task)
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
- } else {
- shellTaskOrganizer.applyTransaction(wct)
+ fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
+ windowDecor.incrementRelayoutBlock()
+ moveToFullscreenWithAnimation(task, task.positionInParent)
}
}
@@ -318,9 +335,8 @@
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
wct.setBounds(task.token, Rect())
- wct.setDensityDpi(task.token, getDefaultDensityDpi())
+ addMoveToSplitChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -328,34 +344,41 @@
}
}
+ private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
+ if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
+ splitScreenController.prepareExitSplitScreen(
+ wct,
+ splitScreenController.getStageOfTask(taskInfo.taskId),
+ EXIT_REASON_ENTER_DESKTOP
+ )
+ getOtherSplitTask(taskInfo.taskId)?.let { otherTaskInfo ->
+ wct.removeTask(otherTaskInfo.token)
+ }
+ }
+ }
+
+ private fun getOtherSplitTask(taskId: Int): RunningTaskInfo? {
+ val remainingTaskPosition: Int =
+ if (splitScreenController.getSplitPosition(taskId)
+ == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
+ return splitScreenController.getTaskInfo(remainingTaskPosition)
+ }
+
/**
- * The second part of the animated move to desktop transition, called after
- * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
- * and released back into status bar area.
+ * The second part of the animated drag to desktop transition, called after
+ * [startDragToDesktop].
*/
- fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
+ fun cancelDragToDesktop(task: RunningTaskInfo) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: cancelMoveToDesktop taskId=%d",
+ "DesktopTasksController: cancelDragToDesktop taskId=%d",
task.taskId
)
- val wct = WindowContainerTransaction()
- wct.setBounds(task.token, Rect())
-
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
- moveToDesktopAnimator) { t ->
- val callbackWCT = WindowContainerTransaction()
- visualIndicator?.releaseVisualIndicator(t)
- visualIndicator = null
- addMoveToFullscreenChanges(callbackWCT, task)
- transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
- }
- } else {
- addMoveToFullscreenChanges(wct, task)
- shellTaskOrganizer.applyTransaction(wct)
- releaseVisualIndicator()
- }
+ dragToDesktopTransitionHandler.cancelDragToDesktopTransition()
}
private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) {
@@ -493,6 +516,55 @@
}
}
+ /**
+ * Quick-resize to the right or left half of the stable bounds.
+ *
+ * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
+ */
+ fun snapToHalfScreen(
+ taskInfo: RunningTaskInfo,
+ windowDecor: DesktopModeWindowDecoration,
+ position: SnapPosition
+ ) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+
+ val destinationWidth = stableBounds.width() / 2
+ val destinationBounds = when (position) {
+ SnapPosition.LEFT -> {
+ Rect(
+ stableBounds.left,
+ stableBounds.top,
+ stableBounds.left + destinationWidth,
+ stableBounds.bottom
+ )
+ }
+ SnapPosition.RIGHT -> {
+ Rect(
+ stableBounds.right - destinationWidth,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom
+ )
+ }
+ }
+
+ if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
+
+ val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ toggleResizeDesktopTaskTransitionHandler.startTransition(
+ wct,
+ taskInfo.taskId,
+ windowDecor
+ )
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
@@ -580,6 +652,10 @@
val triggerTask = request.triggerTask
val shouldHandleRequest =
when {
+ recentsAnimationRunning -> {
+ reason = "recents animation is running"
+ false
+ }
// Only handle open or to front transitions
request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
reason = "transition type not handled (${request.type})"
@@ -645,10 +721,7 @@
finishTransaction: SurfaceControl.Transaction
) {
// Add rounded corners to freeform windows
- val ta: TypedArray = context.obtainStyledAttributes(
- intArrayOf(R.attr.dialogCornerRadius))
- val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
- ta.recycle()
+ val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
@@ -745,7 +818,9 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ // Explicitly setting multi-window at task level interferes with animations.
+ // Let task inherit windowing mode once transition is complete instead.
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
@@ -883,6 +958,11 @@
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
+ // TODO(b/301106941): don't show the indicator until the drag-to-desktop animation has
+ // started, or it'll be visible too early on top of the task surface, especially in
+ // the cancel-early case. Also because it shouldn't even be shown in the cancel-early
+ // case since its dismissal is tied to the cancel animation end, which doesn't even run
+ // in cancel-early.
visualIndicator?.createIndicatorWithAnimatedBounds()
}
val indicator = visualIndicator ?: return
@@ -905,7 +985,7 @@
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- finalizeMoveToDesktop(taskInfo, freeformBounds)
+ finalizeDragToDesktop(taskInfo, freeformBounds)
}
private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
@@ -921,17 +1001,17 @@
}
/**
- * Update the corner region for a specified task
+ * Update the exclusion region for a specified task
*/
- fun onTaskCornersChanged(taskId: Int, corner: Region) {
- desktopModeTaskRepository.updateTaskCorners(taskId, corner)
+ fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
+ desktopModeTaskRepository.updateTaskExclusionRegions(taskId, exclusionRegion)
}
/**
- * Remove a previously tracked corner region for a specified task.
+ * Remove a previously tracked exclusion region for a specified task.
*/
- fun removeCornersForTask(taskId: Int) {
- desktopModeTaskRepository.removeTaskCorners(taskId)
+ fun removeExclusionRegionForTask(taskId: Int) {
+ desktopModeTaskRepository.removeExclusionRegion(taskId)
}
/**
@@ -945,16 +1025,16 @@
}
/**
- * Adds a listener to track changes to desktop task corners
+ * Adds a listener to track changes to desktop task gesture exclusion regions
*
* @param listener the listener to add.
* @param callbackExecutor the executor to call the listener on.
*/
- fun setTaskCornerListener(
+ fun setTaskRegionListener(
listener: Consumer<Region>,
callbackExecutor: Executor
) {
- desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
+ desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
private fun dump(pw: PrintWriter, prefix: String) {
@@ -980,7 +1060,7 @@
callbackExecutor: Executor
) {
mainExecutor.execute {
- this@DesktopTasksController.setTaskCornerListener(listener, callbackExecutor)
+ this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
}
}
}
@@ -1037,11 +1117,11 @@
controller = null
}
- override fun showDesktopApps(displayId: Int) {
+ override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) {
ExecutorUtils.executeRemoteCallWithTaskPermission(
controller,
"showDesktopApps"
- ) { c -> c.showDesktopApps(displayId) }
+ ) { c -> c.showDesktopApps(displayId, remoteTransition) }
}
override fun stashDesktopApps(displayId: Int) {
@@ -1117,4 +1197,7 @@
return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
}
}
+
+ /** The positions on a screen that a task can snap to. */
+ enum class SnapPosition { RIGHT, LEFT }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
new file mode 100644
index 0000000..95d7ad5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -0,0 +1,582 @@
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.app.ActivityOptions
+import android.app.ActivityOptions.SourceInfo
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
+import android.app.PendingIntent.FLAG_MUTABLE
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FILL_IN_COMPONENT
+import android.graphics.Rect
+import android.os.IBinder
+import android.os.SystemClock
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+import com.android.wm.shell.transition.Transitions.TransitionHandler
+import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.util.TransitionUtil
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
+import java.util.function.Supplier
+
+/**
+ * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also
+ * handles the cancellation case where the task is dragged back to the status bar area in the same
+ * gesture.
+ */
+class DragToDesktopTransitionHandler(
+ private val context: Context,
+ private val transitions: Transitions,
+ private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+) : TransitionHandler {
+
+ constructor(
+ context: Context,
+ transitions: Transitions,
+ rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ ) : this(
+ context,
+ transitions,
+ rootTaskDisplayAreaOrganizer,
+ Supplier { SurfaceControl.Transaction() }
+ )
+
+ private val rectEvaluator = RectEvaluator(Rect())
+ private val launchHomeIntent = Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+
+ private var dragToDesktopStateListener: DragToDesktopStateListener? = null
+ private var splitScreenController: SplitScreenController? = null
+ private var transitionState: TransitionState? = null
+
+ /** Whether a drag-to-desktop transition is in progress. */
+ val inProgress: Boolean
+ get() = transitionState != null
+
+ /** Sets a listener to receive callback about events during the transition animation. */
+ fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
+ dragToDesktopStateListener = listener
+ }
+
+ /** Setter needed to avoid cyclic dependency. */
+ fun setSplitScreenController(controller: SplitScreenController) {
+ splitScreenController = controller
+ }
+
+ /**
+ * Starts a transition that performs a transient launch of Home so that Home is brought to the
+ * front while still keeping the currently focused task that is being dragged resumed. This
+ * allows the animation handler to reorder the task to the front and to scale it with the
+ * gesture into the desktop area with the Home and wallpaper behind it.
+ *
+ * Note that the transition handler for this transition doesn't call the finish callback until
+ * after one of the "end" or "cancel" transitions is merged into this transition.
+ */
+ fun startDragToDesktopTransition(
+ taskId: Int,
+ dragToDesktopAnimator: MoveToDesktopAnimator,
+ windowDecoration: DesktopModeWindowDecoration
+ ) {
+ if (inProgress) {
+ error("A drag to desktop is already in progress")
+ }
+
+ val options = ActivityOptions.makeBasic().apply {
+ setTransientLaunch()
+ setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis())
+ pendingIntentCreatorBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ }
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ 0 /* requestCode */,
+ launchHomeIntent,
+ FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
+ options.toBundle()
+ )
+ val wct = WindowContainerTransaction()
+ wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle())
+ val startTransitionToken = transitions
+ .startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
+
+ transitionState = if (isSplitTask(taskId)) {
+ TransitionState.FromSplit(
+ draggedTaskId = taskId,
+ dragAnimator = dragToDesktopAnimator,
+ windowDecoration = windowDecoration,
+ startTransitionToken = startTransitionToken
+ )
+ } else {
+ TransitionState.FromFullscreen(
+ draggedTaskId = taskId,
+ dragAnimator = dragToDesktopAnimator,
+ windowDecoration = windowDecoration,
+ startTransitionToken = startTransitionToken
+ )
+ }
+ }
+
+ /**
+ * Starts a transition that "finishes" the drag to desktop gesture. This transition is intended
+ * to merge into the "start" transition and is the one that actually applies the bounds and
+ * windowing mode changes to the dragged task. This is called when the dragged task is released
+ * inside the desktop drop zone.
+ */
+ fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+ if (requireTransitionState().startAborted) {
+ // Don't attempt to complete the drag-to-desktop since the start transition didn't
+ // succeed as expected. Just reset the state as if nothing happened.
+ clearState()
+ return
+ }
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
+ }
+
+ /**
+ * Starts a transition that "cancels" the drag to desktop gesture. This transition is intended
+ * to merge into the "start" transition and it restores the transient state that was used to
+ * launch the Home task over the dragged task. This is called when the dragged task is released
+ * outside the desktop drop zone and is instead dropped back into the status bar region that
+ * means the user wants to remain in their current windowing mode.
+ */
+ fun cancelDragToDesktopTransition() {
+ val state = requireTransitionState()
+ if (state.startAborted) {
+ // Don't attempt to cancel the drag-to-desktop since the start transition didn't
+ // succeed as expected. Just reset the state as if nothing happened.
+ clearState()
+ return
+ }
+ state.cancelled = true
+ if (state.draggedTaskChange != null) {
+ // Regular case, transient launch of Home happened as is waiting for the cancel
+ // transient to start and merge. Animate the cancellation (scale back to original
+ // bounds) first before actually starting the cancel transition so that the wallpaper
+ // is visible behind the animating task.
+ startCancelAnimation()
+ } else {
+ // There's no dragged task, this can happen when the "cancel" happened too quickly
+ // before the "start" transition is even ready (like on a fling gesture). The
+ // "shrink" animation didn't even start, so there's no need to animate the "cancel".
+ // We also don't want to start the cancel transition yet since we don't have
+ // enough info to restore the order. We'll check for the cancelled state flag when
+ // the "start" animation is ready and cancel from #startAnimation instead.
+ }
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ): Boolean {
+ val state = requireTransitionState()
+
+ val isStartDragToDesktop = info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP &&
+ transition == state.startTransitionToken
+ if (!isStartDragToDesktop) {
+ return false
+ }
+
+ // Layering: non-wallpaper, non-home tasks excluding the dragged task go at the bottom,
+ // then Home on top of that, wallpaper on top of that and finally the dragged task on top
+ // of everything.
+ val appLayers = info.changes.size
+ val homeLayers = info.changes.size * 2
+ val wallpaperLayers = info.changes.size * 3
+ val dragLayer = wallpaperLayers
+ val leafTaskFilter = TransitionUtil.LeafTaskFilter()
+ info.changes.withIndex().forEach { (i, change) ->
+ if (TransitionUtil.isWallpaper(change)) {
+ val layer = wallpaperLayers - i
+ startTransaction.apply {
+ setLayer(change.leash, layer)
+ show(change.leash)
+ }
+ } else if (isHomeChange(change)) {
+ state.homeToken = change.container
+ val layer = homeLayers - i
+ startTransaction.apply {
+ setLayer(change.leash, layer)
+ show(change.leash)
+ }
+ } else if (TransitionInfo.isIndependent(change, info)) {
+ // Root.
+ when (state) {
+ is TransitionState.FromSplit -> {
+ state.splitRootChange = change
+ val layer = if (!state.cancelled) {
+ // Normal case, split root goes to the bottom behind everything else.
+ appLayers - i
+ } else {
+ // Cancel-early case, pretend nothing happened so split root stays top.
+ dragLayer
+ }
+ startTransaction.apply {
+ setLayer(change.leash, layer)
+ show(change.leash)
+ }
+ }
+ is TransitionState.FromFullscreen -> {
+ if (change.taskInfo?.taskId == state.draggedTaskId) {
+ state.draggedTaskChange = change
+ val bounds = change.endAbsBounds
+ startTransaction.apply {
+ setLayer(change.leash, dragLayer)
+ setWindowCrop(change.leash, bounds.width(), bounds.height())
+ show(change.leash)
+ }
+ } else {
+ throw IllegalStateException("Expected root to be dragged task")
+ }
+ }
+ }
+ } else if (leafTaskFilter.test(change)) {
+ // When dragging one of the split tasks, the dragged leaf needs to be re-parented
+ // so that it can be layered separately from the rest of the split root/stages.
+ // The split root including the other split side was layered behind the wallpaper
+ // and home while the dragged split needs to be layered in front of them.
+ // Do not do this in the cancel-early case though, since in that case nothing should
+ // happen on screen so the layering will remain the same as if no transition
+ // occurred.
+ if (change.taskInfo?.taskId == state.draggedTaskId && !state.cancelled) {
+ state.draggedTaskChange = change
+ taskDisplayAreaOrganizer.reparentToDisplayArea(
+ change.endDisplayId, change.leash, startTransaction)
+ val bounds = change.endAbsBounds
+ startTransaction.apply {
+ setLayer(change.leash, dragLayer)
+ setWindowCrop(change.leash, bounds.width(), bounds.height())
+ show(change.leash)
+ }
+ }
+ }
+ }
+ state.startTransitionFinishCb = finishCallback
+ state.startTransitionFinishTransaction = finishTransaction
+ startTransaction.apply()
+
+ if (!state.cancelled) {
+ // Normal case, start animation to scale down the dragged task. It'll also be moved to
+ // follow the finger and when released we'll start the next phase/transition.
+ state.dragAnimator.startAnimation()
+ } else {
+ // Cancel-early case, the state was flagged was cancelled already, which means the
+ // gesture ended in the cancel region. This can happen even before the start transition
+ // is ready/animate here when cancelling quickly like with a fling. There's no point
+ // in starting the scale down animation that we would scale up anyway, so just jump
+ // directly into starting the cancel transition to restore WM order. Surfaces should
+ // not move as if no transition happened.
+ startCancelDragToDesktopTransition()
+ }
+ return true
+ }
+
+ override fun mergeAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ t: SurfaceControl.Transaction,
+ mergeTarget: IBinder,
+ finishCallback: Transitions.TransitionFinishCallback
+ ) {
+ val state = requireTransitionState()
+ val isCancelTransition = info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
+ transition == state.cancelTransitionToken &&
+ mergeTarget == state.startTransitionToken
+ val isEndTransition = info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP &&
+ mergeTarget == state.startTransitionToken
+
+ val startTransactionFinishT = state.startTransitionFinishTransaction
+ ?: error("Start transition expected to be waiting for merge but wasn't")
+ val startTransitionFinishCb = state.startTransitionFinishCb
+ ?: error("Start transition expected to be waiting for merge but wasn't")
+ if (isEndTransition) {
+ info.changes.withIndex().forEach { (i, change) ->
+ if (change.mode == TRANSIT_CLOSE) {
+ t.hide(change.leash)
+ startTransactionFinishT.hide(change.leash)
+ } else if (change.taskInfo?.taskId == state.draggedTaskId) {
+ t.show(change.leash)
+ startTransactionFinishT.show(change.leash)
+ state.draggedTaskChange = change
+ } else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) {
+ // Other freeform tasks that are being restored go behind the dragged task.
+ val draggedTaskLeash = state.draggedTaskChange?.leash
+ ?: error("Expected dragged leash to be non-null")
+ t.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+ startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+ }
+ }
+
+ val draggedTaskChange = state.draggedTaskChange
+ ?: throw IllegalStateException("Expected non-null change of dragged task")
+ val draggedTaskLeash = draggedTaskChange.leash
+ val startBounds = draggedTaskChange.startAbsBounds
+ val endBounds = draggedTaskChange.endAbsBounds
+
+ // TODO(b/301106941): Instead of forcing-finishing the animation that scales the
+ // surface down and then starting another that scales it back up to the final size,
+ // blend the two animations.
+ state.dragAnimator.endAnimator()
+ // Using [DRAG_FREEFORM_SCALE] to calculate animated width/height is possible because
+ // it is known that the animation scale is finished because the animation was
+ // force-ended above. This won't be true when the two animations are blended.
+ val animStartWidth = (startBounds.width() * DRAG_FREEFORM_SCALE).toInt()
+ val animStartHeight = (startBounds.height() * DRAG_FREEFORM_SCALE).toInt()
+ // Using end bounds here to find the left/top also assumes the center animation has
+ // finished and the surface is placed exactly in the center of the screen which matches
+ // the end/default bounds of the now freeform task.
+ val animStartLeft = endBounds.centerX() - (animStartWidth / 2)
+ val animStartTop = endBounds.centerY() - (animStartHeight / 2)
+ val animStartBounds = Rect(
+ animStartLeft,
+ animStartTop,
+ animStartLeft + animStartWidth,
+ animStartTop + animStartHeight
+ )
+
+
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
+ t.apply {
+ setScale(draggedTaskLeash, 1f, 1f)
+ setPosition(
+ draggedTaskLeash,
+ animStartBounds.left.toFloat(),
+ animStartBounds.top.toFloat()
+ )
+ setWindowCrop(
+ draggedTaskLeash,
+ animStartBounds.width(),
+ animStartBounds.height()
+ )
+ }
+ // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+ // and finish callback. Show the veil and position the task at the first frame before
+ // starting the final animation.
+ state.windowDecoration.showResizeVeil(t, animStartBounds)
+ finishCallback.onTransitionFinished(null /* wct */)
+
+ // Because the task surface was scaled down during the drag, we must use the animated
+ // bounds instead of the [startAbsBounds].
+ val tx: SurfaceControl.Transaction = transactionSupplier.get()
+ ValueAnimator.ofObject(rectEvaluator, animStartBounds, endBounds)
+ .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+ .apply {
+ addUpdateListener { animator ->
+ val animBounds = animator.animatedValue as Rect
+ tx.apply {
+ setScale(draggedTaskLeash, 1f, 1f)
+ setPosition(
+ draggedTaskLeash,
+ animBounds.left.toFloat(),
+ animBounds.top.toFloat()
+ )
+ setWindowCrop(
+ draggedTaskLeash,
+ animBounds.width(),
+ animBounds.height()
+ )
+ }
+ state.windowDecoration.updateResizeVeil(tx, animBounds)
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ state.windowDecoration.hideResizeVeil()
+ startTransitionFinishCb.onTransitionFinished(null /* null */)
+ clearState()
+ }
+ })
+ start()
+ }
+ } else if (isCancelTransition) {
+ info.changes.forEach { change ->
+ t.show(change.leash)
+ startTransactionFinishT.show(change.leash)
+ }
+ t.apply()
+ finishCallback.onTransitionFinished(null /* wct */)
+ startTransitionFinishCb.onTransitionFinished(null /* wct */)
+ clearState()
+ }
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ // Only handle transitions started from shell.
+ return null
+ }
+
+ override fun onTransitionConsumed(
+ transition: IBinder,
+ aborted: Boolean,
+ finishTransaction: SurfaceControl.Transaction?
+ ) {
+ val state = transitionState ?: return
+ if (aborted && state.startTransitionToken == transition) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: onTransitionConsumed() start transition aborted"
+ )
+ state.startAborted = true
+ }
+ }
+
+ private fun isHomeChange(change: Change): Boolean {
+ return change.taskInfo?.activityType == ACTIVITY_TYPE_HOME
+ }
+
+ private fun startCancelAnimation() {
+ val state = requireTransitionState()
+ val dragToDesktopAnimator = state.dragAnimator
+
+ val draggedTaskChange = state.draggedTaskChange
+ ?: throw IllegalStateException("Expected non-null task change")
+ val sc = draggedTaskChange.leash
+ // TODO(b/301106941): Don't end the animation and start one to scale it back, merge them
+ // instead.
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ dragToDesktopAnimator.endAnimator()
+ // Then animate the scaled window back to its original bounds.
+ val x: Float = dragToDesktopAnimator.position.x
+ val y: Float = dragToDesktopAnimator.position.y
+ val targetX = draggedTaskChange.endAbsBounds.left
+ val targetY = draggedTaskChange.endAbsBounds.top
+ val dx = targetX - x
+ val dy = targetY - y
+ val tx: SurfaceControl.Transaction = transactionSupplier.get()
+ ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
+ .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+ .apply {
+ addUpdateListener { animator ->
+ val scale = animator.animatedValue as Float
+ val fraction = animator.animatedFraction
+ val animX = x + (dx * fraction)
+ val animY = y + (dy * fraction)
+ tx.apply {
+ setPosition(sc, animX, animY)
+ setScale(sc, scale, scale)
+ show(sc)
+ apply()
+ }
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ dragToDesktopStateListener?.onCancelToDesktopAnimationEnd(tx)
+ // Start the cancel transition to restore order.
+ startCancelDragToDesktopTransition()
+ }
+ })
+ start()
+ }
+ }
+
+ private fun startCancelDragToDesktopTransition() {
+ val state = requireTransitionState()
+ val wct = WindowContainerTransaction()
+ when (state) {
+ is TransitionState.FromFullscreen -> {
+ val wc = state.draggedTaskChange?.container
+ ?: error("Dragged task should be non-null before cancelling")
+ wct.reorder(wc, true /* toTop */)
+ }
+ is TransitionState.FromSplit -> {
+ val wc = state.splitRootChange?.container
+ ?: error("Split root should be non-null before cancelling")
+ wct.reorder(wc, true /* toTop */)
+ }
+ }
+ val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling")
+ wct.restoreTransientOrder(homeWc)
+
+ state.cancelTransitionToken = transitions.startTransition(
+ TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this)
+ }
+
+ private fun clearState() {
+ transitionState = null
+ }
+
+ private fun isSplitTask(taskId: Int): Boolean {
+ return splitScreenController?.isTaskInSplitScreen(taskId) ?: false
+ }
+
+ private fun requireTransitionState(): TransitionState {
+ return transitionState ?: error("Expected non-null transition state")
+ }
+
+ interface DragToDesktopStateListener {
+ fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
+ fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction)
+ }
+
+ sealed class TransitionState {
+ abstract val draggedTaskId: Int
+ abstract val dragAnimator: MoveToDesktopAnimator
+ abstract val windowDecoration: DesktopModeWindowDecoration
+ abstract val startTransitionToken: IBinder
+ abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
+ abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
+ abstract var cancelTransitionToken: IBinder?
+ abstract var homeToken: WindowContainerToken?
+ abstract var draggedTaskChange: Change?
+ abstract var cancelled: Boolean
+ abstract var startAborted: Boolean
+
+ data class FromFullscreen(
+ override val draggedTaskId: Int,
+ override val dragAnimator: MoveToDesktopAnimator,
+ override val windowDecoration: DesktopModeWindowDecoration,
+ override val startTransitionToken: IBinder,
+ override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
+ override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
+ override var cancelTransitionToken: IBinder? = null,
+ override var homeToken: WindowContainerToken? = null,
+ override var draggedTaskChange: Change? = null,
+ override var cancelled: Boolean = false,
+ override var startAborted: Boolean = false,
+ ) : TransitionState()
+ data class FromSplit(
+ override val draggedTaskId: Int,
+ override val dragAnimator: MoveToDesktopAnimator,
+ override val windowDecoration: DesktopModeWindowDecoration,
+ override val startTransitionToken: IBinder,
+ override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
+ override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
+ override var cancelTransitionToken: IBinder? = null,
+ override var homeToken: WindowContainerToken? = null,
+ override var draggedTaskChange: Change? = null,
+ override var cancelled: Boolean = false,
+ override var startAborted: Boolean = false,
+ var splitRootChange: Change? = null,
+ ) : TransitionState()
+ }
+
+ companion object {
+ /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */
+ private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 1128d91..605600f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -18,12 +18,13 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_TO_DESKTOP;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
@@ -38,11 +39,9 @@
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
-import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -60,8 +59,6 @@
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
- private MoveToDesktopAnimator mMoveToDesktopAnimator;
private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
public EnterDesktopTaskTransitionHandler(
@@ -77,61 +74,6 @@
}
/**
- * Starts Transition of a given type
- * @param type Transition type
- * @param wct WindowContainerTransaction for transition
- * @param onAnimationEndCallback to be called after animation
- */
- private void startTransition(@WindowManager.TransitionType int type,
- @NonNull WindowContainerTransaction wct,
- Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- mOnAnimationFinishedCallback = onAnimationEndCallback;
- final IBinder token = mTransitions.startTransition(type, wct, this);
- mPendingTransitionTokens.add(token);
- }
-
- /**
- * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE
- * @param wct WindowContainerTransaction for transition
- * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
- * to desktop animation
- * @param onAnimationEndCallback to be called after animation
- */
- public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
- @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
- Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- mMoveToDesktopAnimator = moveToDesktopAnimator;
- startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
- onAnimationEndCallback);
- }
-
- /**
- * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
- * @param wct WindowContainerTransaction for transition
- * @param onAnimationEndCallback to be called after animation
- */
- public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
- Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct,
- onAnimationEndCallback);
- }
-
- /**
- * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
- * @param wct WindowContainerTransaction for transition
- * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
- * to desktop animation
- * @param onAnimationEndCallback to be called after animation
- */
- public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
- MoveToDesktopAnimator moveToDesktopAnimator,
- Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- mMoveToDesktopAnimator = moveToDesktopAnimator;
- startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct,
- onAnimationEndCallback);
- }
-
- /**
* Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
* @param wct WindowContainerTransaction for transition
* @param decor {@link DesktopModeWindowDecoration} of task being animated
@@ -139,8 +81,8 @@
public void moveToDesktop(@NonNull WindowContainerTransaction wct,
DesktopModeWindowDecoration decor) {
mDesktopModeWindowDecoration = decor;
- startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct,
- null /* onAnimationEndCallback */);
+ final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this);
+ mPendingTransitionTokens.add(token);
}
@Override
@@ -182,30 +124,11 @@
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP
+ if (type == TRANSIT_MOVE_TO_DESKTOP
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return animateMoveToDesktop(change, startT, finishCallback);
}
- if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
- }
-
- final Rect endBounds = change.getEndAbsBounds();
- if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && !endBounds.isEmpty()) {
- return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
- endBounds);
- }
-
- if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
- endBounds);
- }
-
return false;
}
@@ -248,143 +171,6 @@
return true;
}
- private boolean animateStartDragToDesktopMode(
- @NonNull TransitionInfo.Change change,
- @NonNull SurfaceControl.Transaction startT,
- @NonNull SurfaceControl.Transaction finishT,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
- // to null and we don't require an animation
- final SurfaceControl sc = change.getLeash();
- startT.setWindowCrop(sc, null);
-
- if (mMoveToDesktopAnimator == null
- || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
- Slog.e(TAG, "No animator available for this transition");
- return false;
- }
-
- // Calculate and set position of the task
- final PointF position = mMoveToDesktopAnimator.getPosition();
- startT.setPosition(sc, position.x, position.y);
- finishT.setPosition(sc, position.x, position.y);
-
- startT.apply();
-
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null));
-
- return true;
- }
-
- private boolean animateFinalizeDragToDesktopMode(
- @NonNull TransitionInfo.Change change,
- @NonNull SurfaceControl.Transaction startT,
- @NonNull SurfaceControl.Transaction finishT,
- @NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull Rect endBounds) {
- // This Transition animates a task to freeform bounds after being dragged into freeform
- // mode and brings the remaining freeform tasks to front
- final SurfaceControl sc = change.getLeash();
- startT.setWindowCrop(sc, endBounds.width(),
- endBounds.height());
- startT.apply();
-
- // End the animation that shrinks the window when task is first dragged from fullscreen
- if (mMoveToDesktopAnimator != null) {
- mMoveToDesktopAnimator.endAnimator();
- }
-
- // We want to find the scale of the current bounds relative to the end bounds. The
- // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
- // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
- // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
- final ValueAnimator animator =
- ValueAnimator.ofFloat(
- MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
- animator.addUpdateListener(animation -> {
- final float animationValue = (float) animation.getAnimatedValue();
- t.setScale(sc, animationValue, animationValue);
-
- final float animationWidth = endBounds.width() * animationValue;
- final float animationHeight = endBounds.height() * animationValue;
- final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
- final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
-
- t.setPosition(sc, animationX, animationY);
- t.apply();
- });
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
- }
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null));
- }
- });
-
- animator.start();
- return true;
- }
- private boolean animateCancelDragToDesktopMode(
- @NonNull TransitionInfo.Change change,
- @NonNull SurfaceControl.Transaction startT,
- @NonNull SurfaceControl.Transaction finishT,
- @NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull Rect endBounds) {
- // This Transition animates a task to fullscreen after being dragged from the status
- // bar and then released back into the status bar area
- final SurfaceControl sc = change.getLeash();
- // Hide the first (fullscreen) frame because the animation will start from the smaller
- // scale size.
- startT.hide(sc)
- .setWindowCrop(sc, endBounds.width(), endBounds.height())
- .apply();
-
- if (mMoveToDesktopAnimator == null
- || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
- Slog.e(TAG, "No animator available for this transition");
- return false;
- }
-
- // End the animation that shrinks the window when task is first dragged from fullscreen
- mMoveToDesktopAnimator.endAnimator();
-
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
-
- // Get position of the task
- final float x = mMoveToDesktopAnimator.getPosition().x;
- final float y = mMoveToDesktopAnimator.getPosition().y;
-
- animator.addUpdateListener(animation -> {
- final float scale = (float) animation.getAnimatedValue();
- t.setPosition(sc, x * (1 - scale), y * (1 - scale))
- .setScale(sc, scale, scale)
- .show(sc)
- .apply();
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
- }
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null));
- }
- });
- animator.start();
- return true;
- }
-
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 47edfd4..6bdaf1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode;
import android.app.ActivityManager.RunningTaskInfo;
+import android.window.RemoteTransition;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
/**
@@ -25,7 +26,7 @@
interface IDesktopMode {
/** Show apps on the desktop on the given display */
- void showDesktopApps(int displayId);
+ void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
/** Stash apps on the desktop to allow launching another app from home screen */
void stashDesktopApps(int displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 0bf8ec3..fdfb6f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -94,6 +94,7 @@
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
+ // Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
/**
@@ -362,7 +363,7 @@
*/
private boolean isReadyToHandleDrag() {
for (int i = 0; i < mDisplayDropTargets.size(); i++) {
- if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+ if (mDisplayDropTargets.valueAt(i).hasDrawn) {
return true;
}
}
@@ -398,8 +399,13 @@
* Dumps information about this controller.
*/
public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(prefix + " listeners=" + mListeners.size());
+ pw.println(innerPrefix + "listeners=" + mListeners.size());
+ pw.println(innerPrefix + "Per display:");
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ mDisplayDropTargets.valueAt(i).dump(pw, innerPrefix);
+ }
}
/**
@@ -440,7 +446,7 @@
final FrameLayout rootView;
final DragLayout dragLayout;
// Tracks whether the window has fully drawn since it was last made visible
- boolean mHasDrawn;
+ boolean hasDrawn;
boolean isHandlingDrag;
// A count of the number of active drags in progress to ensure that we only hide the window
@@ -464,17 +470,29 @@
rootView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
rootView.requestApplyInsets();
- if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+ if (!hasDrawn && rootView.getViewRootImpl() != null) {
rootView.getViewRootImpl().registerRtFrameCallback(this);
}
} else {
- mHasDrawn = false;
+ hasDrawn = false;
}
}
@Override
public void onFrameDraw(long frame) {
- mHasDrawn = true;
+ hasDrawn = true;
+ }
+
+ /**
+ * Dumps information about this display's shell drop target.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(innerPrefix + "displayId=" + displayId);
+ pw.println(innerPrefix + "hasDrawn=" + hasDrawn);
+ pw.println(innerPrefix + "isHandlingDrag=" + isHandlingDrag);
+ pw.println(innerPrefix + "activeDragCount=" + activeDragCount);
+ dragLayout.dump(pw, innerPrefix);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index e70768b..a31a773 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -40,6 +40,7 @@
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
@@ -138,7 +139,7 @@
final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
final Rect fullscreenDrawRegion = new Rect(displayRegion);
final Rect fullscreenHitRegion = new Rect(displayRegion);
- final boolean inLandscape = mSession.displayLayout.isLandscape();
+ final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
final float dividerWidth = mContext.getResources().getDimensionPixelSize(
R.dimen.split_divider_bar_width);
@@ -155,7 +156,7 @@
topOrLeftBounds.intersect(displayRegion);
bottomOrRightBounds.intersect(displayRegion);
- if (inLandscape) {
+ if (isLeftRightSplit) {
final Rect leftHitRegion = new Rect();
final Rect rightHitRegion = new Rect();
@@ -246,8 +247,15 @@
@SplitPosition int position) {
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
- final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
- ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
+ final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+ baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+ final Bundle opts = baseActivityOpts.toBundle();
+ if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+ opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+ }
+ // Put BAL flags to avoid activity start aborted.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
if (isTask) {
@@ -259,9 +267,6 @@
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
- // Put BAL flags to avoid activity start aborted.
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
position, opts);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 205a455..445ba89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -47,14 +48,18 @@
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import java.io.PrintWriter;
import java.util.ArrayList;
/**
@@ -74,6 +79,11 @@
private final StatusBarManager mStatusBarManager;
private final Configuration mLastConfiguration = new Configuration();
+ // Whether this device supports left/right split in portrait
+ private final boolean mAllowLeftRightSplitInPortrait;
+ // Whether the device is currently in left/right split mode
+ private boolean mIsLeftRightSplit;
+
private DragAndDropPolicy.Target mCurrentTarget = null;
private DropZoneView mDropZoneView1;
private DropZoneView mDropZoneView2;
@@ -106,17 +116,18 @@
setLayoutDirection(LAYOUT_DIRECTION_LTR);
mDropZoneView1 = new DropZoneView(context);
mDropZoneView2 = new DropZoneView(context);
- addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT));
- addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT));
+ addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
- int orientation = getResources().getConfiguration().orientation;
- setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
- ? LinearLayout.HORIZONTAL
- : LinearLayout.VERTICAL);
- updateContainerMargins(getResources().getConfiguration().orientation);
+ // We don't use the configuration orientation here to determine landscape because
+ // near-square devices may report the same orietation with insets taken into account
+ mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(
+ context.getResources());
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ getResources().getConfiguration());
+ setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ updateContainerMargins(mIsLeftRightSplit);
}
@Override
@@ -124,11 +135,12 @@
mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
- final int orientation = getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ boolean isLeftRightSplit = mSplitScreenController != null
+ && mSplitScreenController.isLeftRightSplit();
+ if (isLeftRightSplit) {
mDropZoneView1.setBottomInset(mInsets.bottom);
mDropZoneView2.setBottomInset(mInsets.bottom);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ } else {
mDropZoneView1.setBottomInset(0);
mDropZoneView2.setBottomInset(mInsets.bottom);
}
@@ -136,14 +148,12 @@
}
public void onConfigChanged(Configuration newConfig) {
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
- && getOrientation() != HORIZONTAL) {
- setOrientation(LinearLayout.HORIZONTAL);
- updateContainerMargins(newConfig.orientation);
- } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- && getOrientation() != VERTICAL) {
- setOrientation(LinearLayout.VERTICAL);
- updateContainerMargins(newConfig.orientation);
+ boolean isLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ newConfig);
+ if (isLeftRightSplit != mIsLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
+ setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ updateContainerMargins(mIsLeftRightSplit);
}
final int diff = newConfig.diff(mLastConfiguration);
@@ -162,14 +172,14 @@
mDropZoneView2.setContainerMargin(0, 0, 0, 0);
}
- private void updateContainerMargins(int orientation) {
+ private void updateContainerMargins(boolean isLeftRightSplit) {
final float halfMargin = mDisplayMargin / 2f;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (isLeftRightSplit) {
mDropZoneView1.setContainerMargin(
mDisplayMargin, mDisplayMargin, halfMargin, mDisplayMargin);
mDropZoneView2.setContainerMargin(
halfMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ } else {
mDropZoneView1.setContainerMargin(
mDisplayMargin, mDisplayMargin, mDisplayMargin, halfMargin);
mDropZoneView2.setContainerMargin(
@@ -257,23 +267,21 @@
* @param bounds2 bounds to apply to the second dropzone view, null if split in half.
*/
private void updateDropZoneSizes(Rect bounds1, Rect bounds2) {
- final int orientation = getResources().getConfiguration().orientation;
- final boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
final int halfDivider = mDividerSize / 2;
final LinearLayout.LayoutParams dropZoneView1 =
(LayoutParams) mDropZoneView1.getLayoutParams();
final LinearLayout.LayoutParams dropZoneView2 =
(LayoutParams) mDropZoneView2.getLayoutParams();
- if (isPortrait) {
- dropZoneView1.width = MATCH_PARENT;
- dropZoneView2.width = MATCH_PARENT;
- dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
- dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
- } else {
+ if (mIsLeftRightSplit) {
dropZoneView1.width = bounds1 != null ? bounds1.width() + halfDivider : MATCH_PARENT;
dropZoneView2.width = bounds2 != null ? bounds2.width() + halfDivider : MATCH_PARENT;
dropZoneView1.height = MATCH_PARENT;
dropZoneView2.height = MATCH_PARENT;
+ } else {
+ dropZoneView1.width = MATCH_PARENT;
+ dropZoneView2.width = MATCH_PARENT;
+ dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
+ dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
}
dropZoneView1.weight = bounds1 != null ? 0 : 1;
dropZoneView2.weight = bounds2 != null ? 0 : 1;
@@ -371,7 +379,7 @@
// Reset the state if we previously force-ignore the bottom margin
mDropZoneView1.setForceIgnoreBottomMargin(false);
mDropZoneView2.setForceIgnoreBottomMargin(false);
- updateContainerMargins(getResources().getConfiguration().orientation);
+ updateContainerMargins(mIsLeftRightSplit);
mCurrentTarget = null;
}
@@ -481,4 +489,19 @@
final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
}
+
+ /**
+ * Dumps information about this drag layout.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "DragLayout:");
+ pw.println(innerPrefix + "mIsLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+ pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "mDisplayMargin=" + mDisplayMargin);
+ pw.println(innerPrefix + "mDividerSize=" + mDividerSize);
+ pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
+ pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
+ pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 478b6a9..353d702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,31 +18,17 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
-import static android.content.Intent.EXTRA_USER;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.os.UserHandle;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
import com.android.wm.shell.common.DisplayLayout;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 22541bbd..a80241e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -68,7 +68,7 @@
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mShellTaskOrganizer.addFocusListener(this);
}
}
@@ -90,7 +90,7 @@
t.apply();
}
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
if (taskInfo.isVisible) {
@@ -111,7 +111,7 @@
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
@@ -135,7 +135,7 @@
taskInfo.taskId);
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
@@ -154,7 +154,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
- if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+ if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 53b5bd7..e63bbc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -49,6 +50,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -58,10 +61,12 @@
*
* <p>This takes the highest priority.
*/
-public class KeyguardTransitionHandler implements Transitions.TransitionHandler {
+public class KeyguardTransitionHandler
+ implements Transitions.TransitionHandler, KeyguardChangeListener {
private static final String TAG = "KeyguardTransition";
private final Transitions mTransitions;
+ private final ShellController mShellController;
private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
@@ -80,6 +85,9 @@
// transition.
private boolean mIsLaunchingActivityOverLockscreen;
+ // Last value reported by {@link KeyguardChangeListener}.
+ private boolean mKeyguardShowing = true;
+
private final class StartedTransition {
final TransitionInfo mInfo;
final SurfaceControl.Transaction mFinishT;
@@ -92,12 +100,15 @@
mPlayer = player;
}
}
+
public KeyguardTransitionHandler(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull Transitions transitions,
@NonNull Handler mainHandler,
@NonNull ShellExecutor mainExecutor) {
mTransitions = transitions;
+ mShellController = shellController;
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
@@ -105,6 +116,7 @@
private void onInit() {
mTransitions.addHandler(this);
+ mShellController.addKeyguardChangeListener(this);
}
/**
@@ -120,6 +132,16 @@
}
@Override
+ public void onKeyguardVisibilityChanged(
+ boolean visible, boolean occluded, boolean animatingDismiss) {
+ mKeyguardShowing = visible;
+ }
+
+ public boolean isKeyguardShowing() {
+ return mKeyguardShowing;
+ }
+
+ @Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -134,24 +156,28 @@
"going-away",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
- if (hasOpeningDream(info)) {
- return startAnimation(mOccludeByDreamTransition,
- "occlude-by-dream",
- transition, info, startTransaction, finishTransaction, finishCallback);
- } else {
- return startAnimation(mOccludeTransition,
- "occlude",
+
+ // Occlude/unocclude animations are only played if the keyguard is locked.
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
+ if (hasOpeningDream(info)) {
+ return startAnimation(mOccludeByDreamTransition,
+ "occlude-by-dream",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ } else {
+ return startAnimation(mOccludeTransition,
+ "occlude",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ }
+ } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ return startAnimation(mUnoccludeTransition,
+ "unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- return startAnimation(mUnoccludeTransition,
- "unocclude",
- transition, info, startTransaction, finishTransaction, finishCallback);
- } else {
- Log.i(TAG, "Refused to play keyguard transition: " + info);
- return false;
}
+
+ Log.i(TAG, "Refused to play keyguard transition: " + info);
+ return false;
}
private boolean startAnimation(IRemoteTransition remoteHandler, String description,
@@ -226,12 +252,7 @@
Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
}
nextFinishCallback.onTransitionFinished(null);
- } else if (nextInfo.getType() == TRANSIT_SLEEP) {
- // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
- // token is held. In cases where keyguard is showing, we are running the animation for
- // the device sleeping/waking, so it's best to ignore this and keep playing anyway.
- return;
- } else if (handles(nextInfo)) {
+ } else {
// In all other cases, fast-forward to let the next queued transition start playing.
finishAnimationImmediately(currentTransition, playing);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 38ce164..d157ca8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -16,8 +16,8 @@
package com.android.wm.shell.onehanded;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
@@ -37,6 +37,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.jank.Cuj.CujType;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
@@ -327,7 +328,7 @@
mTransitionCallbacks.add(callback);
}
- void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) {
+ void beginCUJTracing(@CujType int cujType, @Nullable String tag) {
final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry =
getDisplayAreaTokenMap().entrySet().iterator().next();
final InteractionJankMonitor.Configuration.Builder builder =
@@ -339,11 +340,11 @@
mJankMonitor.begin(builder);
}
- void endCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+ void endCUJTracing(@CujType int cujType) {
mJankMonitor.end(cujType);
}
- void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+ void cancelCUJTracing(@CujType int cujType) {
mJankMonitor.cancel(cujType);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
new file mode 100644
index 0000000..f7977f8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.performance
+
+import android.content.Context
+import android.os.PerformanceHintManager
+import android.os.Process
+import android.window.SystemPerformanceHinter
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import java.io.PrintWriter
+import java.util.concurrent.TimeUnit
+
+/**
+ * Manages the performance hints to the system.
+ */
+class PerfHintController(private val mContext: Context,
+ shellInit: ShellInit,
+ private val mShellCommandHandler: ShellCommandHandler,
+ rootTdaOrganizer: RootTaskDisplayAreaOrganizer) {
+
+ // The system perf hinter
+ val hinter: SystemPerformanceHinter
+
+ init {
+ hinter = SystemPerformanceHinter(mContext,
+ rootTdaOrganizer.performanceRootProvider)
+ shellInit.addInitCallback(this::onInit, this)
+ }
+
+ private fun onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this)
+ val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java)
+ val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()),
+ TimeUnit.SECONDS.toNanos(1))
+ hinter.setAdpfSession(adpfSession)
+ }
+
+ fun dump(pw: PrintWriter, prefix: String?) {
+ hinter.dump(pw, prefix)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 57cc28d..4c47737 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -427,10 +427,10 @@
new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
}
- void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
- int appIconSizePx) {
+ void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int appIconSizePx) {
reattachContentOverlay(
- new PipContentOverlay.PipAppIconOverlay(context, bounds,
+ new PipContentOverlay.PipAppIconOverlay(context, appBounds, destinationBounds,
new IconProvider(context).getIcon(activityInfo), appIconSizePx));
}
@@ -769,7 +769,7 @@
getSurfaceTransactionHelper().crop(tx, leash, destBounds);
}
if (mContentOverlay != null) {
- mContentOverlay.onAnimationEnd(tx, destBounds);
+ clearContentOverlay();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index c701b95..e11e859 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -67,15 +67,6 @@
public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
Rect currentBounds, float fraction);
- /**
- * Callback when reaches the end of animation on the internal {@link #mLeash}.
- * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
- * call apply on this transaction, it should be applied on the caller side.
- * @param destinationBounds {@link Rect} of the final bounds.
- */
- public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx,
- Rect destinationBounds);
-
/** A {@link PipContentOverlay} uses solid color. */
public static final class PipColorOverlay extends PipContentOverlay {
private static final String TAG = PipColorOverlay.class.getSimpleName();
@@ -107,11 +98,6 @@
atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
- @Override
- public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- // Do nothing. Color overlay should be fully opaque by now.
- }
-
private float[] getContentOverlayColor(Context context) {
final TypedArray ta = context.obtainStyledAttributes(new int[] {
android.R.attr.colorBackground });
@@ -164,11 +150,6 @@
Rect currentBounds, float fraction) {
// Do nothing. Keep the snapshot till animation ends.
}
-
- @Override
- public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- atomicTx.remove(mLeash);
- }
}
/** A {@link PipContentOverlay} shows app icon on solid color background. */
@@ -180,20 +161,27 @@
private final Context mContext;
private final int mAppIconSizePx;
private final Rect mAppBounds;
+ private final int mOverlayHalfSize;
private final Matrix mTmpTransform = new Matrix();
private final float[] mTmpFloat9 = new float[9];
private Bitmap mBitmap;
- public PipAppIconOverlay(Context context, Rect appBounds,
+ public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds,
Drawable appIcon, int appIconSizePx) {
mContext = context;
final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
- mAppBounds = new Rect(appBounds);
- mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
- Bitmap.Config.ARGB_8888);
+
+ final int overlaySize = getOverlaySize(appBounds, destinationBounds);
+ mOverlayHalfSize = overlaySize >> 1;
+
+ // When the activity is in the secondary split, make sure the scaling center is not
+ // offset.
+ mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
+
+ mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
prepareAppIconOverlay(appIcon);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
@@ -201,6 +189,21 @@
.build();
}
+ /**
+ * Returns the size of the app icon overlay.
+ *
+ * In order to have the overlay always cover the pip window during the transition,
+ * the overlay will be drawn with the max size of the start and end bounds in different
+ * rotation.
+ */
+ public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
+
+ return Math.max(Math.max(appWidth, appHeight),
+ Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+ }
+
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
tx.show(mLeash);
@@ -215,22 +218,24 @@
public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
Rect currentBounds, float fraction) {
mTmpTransform.reset();
+ // In order for the overlay to always cover the pip window, the overlay may have a
+ // size larger than the pip window. Make sure that app icon is at the center.
+ final int appBoundsCenterX = mAppBounds.centerX();
+ final int appBoundsCenterY = mAppBounds.centerY();
+ mTmpTransform.setTranslate(
+ appBoundsCenterX - mOverlayHalfSize,
+ appBoundsCenterY - mOverlayHalfSize);
// Scale back the bitmap with the pivot point at center.
mTmpTransform.postScale(
(float) mAppBounds.width() / currentBounds.width(),
(float) mAppBounds.height() / currentBounds.height(),
- mAppBounds.centerX(),
- mAppBounds.centerY());
+ appBoundsCenterX,
+ appBoundsCenterY);
atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
@Override
- public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- atomicTx.remove(mLeash);
- }
-
- @Override
public void detach(SurfaceControl.Transaction tx) {
super.detach(tx);
if (mBitmap != null && !mBitmap.isRecycled()) {
@@ -253,10 +258,10 @@
ta.recycle();
}
final Rect appIconBounds = new Rect(
- mAppBounds.centerX() - mAppIconSizePx / 2,
- mAppBounds.centerY() - mAppIconSizePx / 2,
- mAppBounds.centerX() + mAppIconSizePx / 2,
- mAppBounds.centerY() + mAppIconSizePx / 2);
+ mOverlayHalfSize - mAppIconSizePx / 2,
+ mOverlayHalfSize - mAppIconSizePx / 2,
+ mOverlayHalfSize + mAppIconSizePx / 2,
+ mOverlayHalfSize + mAppIconSizePx / 2);
appIcon.setBounds(appIconBounds);
appIcon.draw(canvas);
mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index cbed4b5..a58d94e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -81,15 +81,35 @@
*/
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, RectF destinationBounds) {
return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
}
/**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, float degrees) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+ }
+
+ /**
* Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, Rect destinationBounds, float degrees) {
+ Rect sourceBounds, RectF destinationBounds, float degrees) {
mTmpSourceRectF.set(sourceBounds);
// We want the matrix to position the surface relative to the screen coordinates so offset
// the source to 0,0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 9e8f9c6..3635165 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -120,6 +121,10 @@
*/
private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
+ private static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS =
+ SystemProperties.getInt(
+ "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
+
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
@@ -151,74 +156,77 @@
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
- private boolean mIsCancelled;
- @Override
- public void onPipAnimationStart(TaskInfo taskInfo,
- PipAnimationController.PipTransitionAnimator animator) {
- final int direction = animator.getTransitionDirection();
- mIsCancelled = false;
- sendOnPipTransitionStarted(direction);
- }
+ private boolean mIsCancelled;
- @Override
- public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
- PipAnimationController.PipTransitionAnimator animator) {
- final int direction = animator.getTransitionDirection();
- if (mIsCancelled) {
- sendOnPipTransitionFinished(direction);
- maybePerformFinishResizeCallback();
- return;
- }
- final int animationType = animator.getAnimationType();
- final Rect destinationBounds = animator.getDestinationBounds();
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay*/);
- }
- if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
- && direction == TRANSITION_DIRECTION_TO_PIP) {
- // Notify the display to continue the deferred orientation change.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.scheduleFinishEnterPip(mToken, destinationBounds);
- mTaskOrganizer.applyTransaction(wct);
- // The final task bounds will be applied by onFixedRotationFinished so that all
- // coordinates are in new rotation.
- mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
- mDeferredAnimEndTransaction = tx;
- return;
- }
- final boolean isExitPipDirection = isOutPipDirection(direction)
- || isRemovePipDirection(direction);
- if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
- || isExitPipDirection) {
- // execute the finish resize callback if needed after the transaction is committed
- tx.addTransactionCommittedListener(mMainExecutor,
- PipTaskOrganizer.this::maybePerformFinishResizeCallback);
+ @Override
+ public void onPipAnimationStart(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ mIsCancelled = false;
+ sendOnPipTransitionStarted(direction);
+ }
- // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
- // the end of an exit PIP animation.
- // This is necessary in case there was a resize animation ongoing when exit PIP
- // started, in which case the first resize will be skipped to let the exit
- // operation handle the final resize out of PIP mode. See b/185306679.
- finishResizeDelayedIfNeeded(() -> {
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
- });
- }
- }
+ @Override
+ public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ if (mIsCancelled) {
+ sendOnPipTransitionFinished(direction);
+ maybePerformFinishResizeCallback();
+ return;
+ }
+ final int animationType = animator.getAnimationType();
+ final Rect destinationBounds = animator.getDestinationBounds();
+ if (isInPipDirection(direction) && mPipOverlay != null) {
+ fadeOutAndRemoveOverlay(mPipOverlay,
+ null /* callback */, true /* withStartDelay*/);
+ }
+ if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
+ && direction == TRANSITION_DIRECTION_TO_PIP) {
+ // Notify the display to continue the deferred orientation change.
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.scheduleFinishEnterPip(mToken, destinationBounds);
+ mTaskOrganizer.applyTransaction(wct);
+ // The final task bounds will be applied by onFixedRotationFinished so
+ // that all coordinates are in new rotation.
+ mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
+ mDeferredAnimEndTransaction = tx;
+ return;
+ }
+ final boolean isExitPipDirection = isOutPipDirection(direction)
+ || isRemovePipDirection(direction);
+ if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
+ || isExitPipDirection) {
+ // execute the finish resize callback if needed after the transaction is
+ // committed
+ tx.addTransactionCommittedListener(mMainExecutor,
+ PipTaskOrganizer.this::maybePerformFinishResizeCallback);
- @Override
- public void onPipAnimationCancel(TaskInfo taskInfo,
- PipAnimationController.PipTransitionAnimator animator) {
- final int direction = animator.getTransitionDirection();
- mIsCancelled = true;
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay */);
- }
- sendOnPipTransitionCancelled(direction);
- }
- };
+ // Finish resize as long as we're not exiting PIP, or, if we are, only if
+ // this is the end of an exit PIP animation.
+ // This is necessary in case there was a resize animation ongoing when
+ // exit PIP started, in which case the first resize will be skipped to
+ // let the exit operation handle the final resize out of PIP mode.
+ // See b/185306679.
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
+ }
+ }
+
+ @Override
+ public void onPipAnimationCancel(TaskInfo taskInfo,
+ PipAnimationController.PipTransitionAnimator animator) {
+ final int direction = animator.getTransitionDirection();
+ mIsCancelled = true;
+ if (isInPipDirection(direction) && mPipOverlay != null) {
+ fadeOutAndRemoveOverlay(mPipOverlay,
+ null /* callback */, true /* withStartDelay */);
+ }
+ sendOnPipTransitionCancelled(direction);
+ }
+ };
/**
* Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
@@ -292,9 +300,9 @@
// changed RunningTaskInfo when it finishes.
private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
private WindowContainerToken mToken;
- private SurfaceControl mLeash;
+ protected SurfaceControl mLeash;
protected PipTransitionState mPipTransitionState;
- private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ protected PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
protected PictureInPictureParams mPictureInPictureParams;
private IntConsumer mOnDisplayIdChangeCallback;
@@ -321,11 +329,10 @@
private @Surface.Rotation int mCurrentRotation;
/**
- * An optional overlay used to mask content changing between an app in/out of PiP, only set if
- * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
+ * An optional overlay used to mask content changing between an app in/out of PiP.
*/
@Nullable
- SurfaceControl mSwipePipToHomeOverlay;
+ SurfaceControl mPipOverlay;
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@@ -465,7 +472,7 @@
return;
}
mPipBoundsState.setBounds(destinationBounds);
- mSwipePipToHomeOverlay = overlay;
+ mPipOverlay = overlay;
if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
// With Shell transition, the overlay was attached to the remote transition leash, which
// will be removed when the current transition is finished, so we need to reparent it
@@ -877,7 +884,7 @@
}
final Rect destinationBounds = mPipBoundsState.getBounds();
- final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay;
+ final SurfaceControl swipeToHomeOverlay = mPipOverlay;
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.resetScale(tx, mLeash, destinationBounds)
@@ -896,7 +903,7 @@
}
}, tx);
mPipTransitionState.setInSwipePipToHomeTransition(false);
- mSwipePipToHomeOverlay = null;
+ mPipOverlay = null;
}
private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
@@ -968,7 +975,7 @@
return;
}
- cancelCurrentAnimator();
+ cancelAnimationOnTaskVanished();
onExitPipFinished(info);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -976,6 +983,10 @@
}
}
+ protected void cancelAnimationOnTaskVanished() {
+ cancelCurrentAnimator();
+ }
+
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
@@ -1095,7 +1106,7 @@
}
/** Called when exiting PIP transition is finished to do the state cleanup. */
- void onExitPipFinished(TaskInfo info) {
+ public void onExitPipFinished(TaskInfo info) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onExitPipFinished: %s, state=%s leash=%s",
info.topActivity, mPipTransitionState, mLeash);
@@ -1107,9 +1118,9 @@
}
clearWaitForFixedRotation();
- if (mSwipePipToHomeOverlay != null) {
- removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */);
- mSwipePipToHomeOverlay = null;
+ if (mPipOverlay != null) {
+ removeContentOverlay(mPipOverlay, null /* callback */);
+ mPipOverlay = null;
}
resetShadowRadius();
mPipTransitionState.setInSwipePipToHomeTransition(false);
@@ -1718,7 +1729,7 @@
sourceHintRect = computeRotatedBounds(rotationDelta, direction, destinationBounds,
sourceHintRect);
}
- Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
+ final Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
? mPipBoundsState.getBounds() : currentBounds;
final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
&& mPipAnimationController.getCurrentAnimator().isRunning();
@@ -1741,7 +1752,7 @@
final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
if (hasTopActivityInfo) {
animator.setAppIconContentOverlay(
- mContext, currentBounds, mTaskInfo.topActivityInfo,
+ mContext, currentBounds, destinationBounds, mTaskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -1757,6 +1768,7 @@
animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
}
}
+ mPipOverlay = animator.getContentOverlayLeash();
// The destination bounds are used for the end rect of animation and the final bounds
// after animation finishes. So after the animation is started, the destination bounds
// can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
@@ -1863,11 +1875,21 @@
removeContentOverlay(surface, callback);
}
});
- animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
+ animator.setStartDelay(withStartDelay
+ ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS
+ : EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
animator.start();
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ if (mPipOverlay != null) {
+ if (mPipOverlay != surface) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: trying to remove overlay (%s) which is not local reference (%s)",
+ TAG, surface, mPipOverlay);
+ }
+ mPipOverlay = null;
+ }
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
return;
@@ -1896,11 +1918,11 @@
private void cancelCurrentAnimator() {
final PipAnimationController.PipTransitionAnimator<?> animator =
mPipAnimationController.getCurrentAnimator();
+ // remove any overlays if present
+ if (mPipOverlay != null) {
+ removeContentOverlay(mPipOverlay, null /* callback */);
+ }
if (animator != null) {
- if (animator.getContentOverlayLeash() != null) {
- removeContentOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay);
- }
PipAnimationController.quietCancel(animator);
mPipAnimationController.resetAnimatorState();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 018d674..f5f15d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -43,6 +43,7 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import android.animation.Animator;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
@@ -76,6 +77,8 @@
import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
/**
@@ -86,6 +89,18 @@
private static final String TAG = PipTransition.class.getSimpleName();
+ /** No fixed rotation, or fixed rotation state is undefined. */
+ private static final int FIXED_ROTATION_UNDEFINED = 0;
+ /**
+ * Fixed rotation detected via callbacks (see PipController#startSwipePipToHome());
+ * this is used in the swipe PiP to home case, since the transitions itself isn't supposed to
+ * see the fixed rotation.
+ */
+ private static final int FIXED_ROTATION_CALLBACK = 1;
+
+ /** Fixed rotation detected in the incoming transition. */
+ private static final int FIXED_ROTATION_TRANSITION = 2;
+
private final Context mContext;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
@@ -106,17 +121,28 @@
/** The Task window that is currently in PIP windowing mode. */
@Nullable
private WindowContainerToken mCurrentPipTaskToken;
- /** Whether display is in fixed rotation. */
- private boolean mInFixedRotation;
+
+ @IntDef(prefix = { "FIXED_ROTATION_" }, value = {
+ FIXED_ROTATION_UNDEFINED,
+ FIXED_ROTATION_CALLBACK,
+ FIXED_ROTATION_TRANSITION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FixedRotationState {}
+
+ /** Fixed rotation state of the display. */
+ private @FixedRotationState int mFixedRotationState = FIXED_ROTATION_UNDEFINED;
/**
* The rotation that the display will apply after expanding PiP to fullscreen. This is only
- * meaningful if {@link #mInFixedRotation} is true.
+ * meaningful if {@link #mFixedRotationState} is {@link #FIXED_ROTATION_TRANSITION}.
*/
@Surface.Rotation
private int mEndFixedRotation;
/** Whether the PIP window has fade out for fixed rotation. */
private boolean mHasFadeOut;
+ private Rect mInitBounds = new Rect();
+
/** Used for setting transform to a transaction from animator. */
private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
new PipAnimationController.PipTransactionHandler() {
@@ -181,8 +207,16 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- mInFixedRotation = fixedRotationChange != null;
- mEndFixedRotation = mInFixedRotation
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+ // If we are just about to process potential fixed rotation information,
+ // then fixed rotation state should either be UNDEFINED or CALLBACK.
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: startAnimation() should start with clear fixed rotation state", TAG);
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+ }
+ mFixedRotationState = fixedRotationChange != null
+ ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
+ mEndFixedRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
? fixedRotationChange.getEndFixedRotation()
: ROTATION_UNDEFINED;
@@ -347,6 +381,10 @@
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {
+ // Transition either finished pre-emptively, got merged, or aborted,
+ // so update fixed rotation state to default.
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
+
if (transition != mExitTransition) {
return;
}
@@ -408,7 +446,8 @@
// done at the start. But if it is running fixed rotation, there will be a seamless
// display transition later. So the last rotation transform needs to be kept to
// avoid flickering, and then the display transition will reset the transform.
- if (!mInFixedRotation && mFinishTransaction != null) {
+ if (mFixedRotationState != FIXED_ROTATION_TRANSITION
+ && mFinishTransaction != null) {
mFinishTransaction.merge(tx);
}
} else {
@@ -426,12 +465,27 @@
mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
+ if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
+ // Resetting the scale for pinned task while re-adjusting its crop,
+ // also scales the overlay. So we need to update the overlay leash too.
+ Rect overlayBounds = new Rect(destinationBounds);
+ final int overlaySize = PipContentOverlay.PipAppIconOverlay
+ .getOverlaySize(mInitBounds, destinationBounds);
+
+ overlayBounds.offsetTo(
+ (destinationBounds.width() - overlaySize) / 2,
+ (destinationBounds.height() - overlaySize) / 2);
+ mSurfaceTransactionHelper.resetScale(tx,
+ mPipOrganizer.mPipOverlay, overlayBounds);
+ }
}
+ mInitBounds.setEmpty();
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
.getDisplayRotation();
- if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
+ if (enteringPip && mFixedRotationState == FIXED_ROTATION_TRANSITION
+ && mEndFixedRotation != displayRotation
&& hasValidLeash) {
// Launcher may update the Shelf height during the animation, which will update the
// destination bounds. Because this is in fixed rotation, We need to make sure the
@@ -451,6 +505,8 @@
mFinishTransaction = null;
callFinishCallback(wct);
}
+ // This is the end of transition on the Shell side so update the fixed rotation state.
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
finishResizeForMenu(destinationBounds);
}
@@ -467,6 +523,7 @@
// mFinishCallback might be null with an outdated mCurrentPipTaskToken
// for example, when app crashes while in PiP and exit transition has not started
mCurrentPipTaskToken = null;
+ mFixedRotationState = FIXED_ROTATION_UNDEFINED;
if (mFinishCallback == null) return;
mFinishCallback.onTransitionFinished(null /* wct */);
mFinishCallback = null;
@@ -475,6 +532,9 @@
@Override
public void onFixedRotationStarted() {
+ if (mFixedRotationState == FIXED_ROTATION_UNDEFINED) {
+ mFixedRotationState = FIXED_ROTATION_CALLBACK;
+ }
fadeEnteredPipIfNeed(false /* show */);
}
@@ -554,6 +614,11 @@
}
}
}
+ // if overlay is present remove it immediately, as exit transition came before it faded out
+ if (mPipOrganizer.mPipOverlay != null) {
+ startTransaction.remove(mPipOrganizer.mPipOverlay);
+ clearPipOverlay();
+ }
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: No window of exiting PIP is found. Can't play expand animation", TAG);
@@ -651,7 +716,7 @@
// Check if it is fixed rotation.
final int rotationDelta;
- if (mInFixedRotation) {
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
final int startRotation = pipChange.getStartRotation();
final int endRotation = mEndFixedRotation;
rotationDelta = deltaRotation(startRotation, endRotation);
@@ -822,14 +887,23 @@
+ "participant");
}
- // Make sure other open changes are visible as entering PIP. Some may be hidden in
- // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+ // Make sure other non-pip changes are handled correctly.
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change == enterPip) continue;
if (TransitionUtil.isOpeningType(change.getMode())) {
+ // For other open changes that are visible when entering PIP, some may be hidden in
+ // Transitions#setupStartState because the transition type is OPEN (such as
+ // auto-enter).
final SurfaceControl leash = change.getLeash();
startTransaction.show(leash).setAlpha(leash, 1.f);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ // For other close changes that are invisible as entering PIP, hide them immediately
+ // to avoid showing a freezing surface.
+ // Ideally, we should let other handler to handle them (likely RemoteHandler by
+ // Launcher).
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.hide(leash);
}
}
@@ -859,11 +933,13 @@
final int startRotation = pipChange.getStartRotation();
// Check again in case some callers use startEnterAnimation directly so the flag was not
// set in startAnimation, e.g. from DefaultMixedHandler.
- if (!mInFixedRotation) {
+ if (mFixedRotationState != FIXED_ROTATION_TRANSITION) {
mEndFixedRotation = pipChange.getEndFixedRotation();
- mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+ mFixedRotationState = mEndFixedRotation != ROTATION_UNDEFINED
+ ? FIXED_ROTATION_TRANSITION : mFixedRotationState;
}
- final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
+ final int endRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION
+ ? mEndFixedRotation : pipChange.getEndRotation();
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
@@ -874,10 +950,15 @@
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
+
+ // Cache the start bounds for overlay manipulations as a part of finishCallback.
+ mInitBounds.set(currentBounds);
+
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
- if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ if (rotationDelta != Surface.ROTATION_0
+ && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
// Need to get the bounds of new rotation in old rotation for fixed rotation,
computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
destinationBounds, sourceHintRect);
@@ -916,7 +997,7 @@
final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
if (hasTopActivityInfo) {
animator.setAppIconContentOverlay(
- mContext, currentBounds, taskInfo.topActivityInfo,
+ mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -941,10 +1022,12 @@
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
+ mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
- if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ if (rotationDelta != Surface.ROTATION_0
+ && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
// For fixed rotation, the animation destination bounds is in old rotation coordinates.
// Set the destination bounds to new coordinates after the animation is finished.
// ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
@@ -983,14 +1066,18 @@
@NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
@NonNull Rect destinationBounds,
@NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
- if (mInFixedRotation) {
+ if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
// If rotation changes when returning to home, the transition should contain both the
// entering PiP and the display change (PipController#startSwipePipToHome has updated
// the display layout to new rotation). So it is not expected to see fixed rotation.
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
}
- final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
+ Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
+ if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
+ mInitBounds.set(appBounds);
+ }
+ final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
// reparent the PIP activity to a new PIP task (in case there are other activities
@@ -998,7 +1085,6 @@
// the overlay to the final PIP task.
startTransaction.reparent(swipePipToHomeOverlay, leash)
.setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
- mPipOrganizer.mSwipePipToHomeOverlay = null;
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1020,7 +1106,7 @@
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
- null /* callback */, false /* withStartDelay */);
+ this::clearPipOverlay /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
}
@@ -1164,6 +1250,10 @@
mPipMenuController.updateMenuBounds(destinationBounds);
}
+ private void clearPipOverlay() {
+ mPipOrganizer.mPipOverlay = null;
+ }
+
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 20c57fa..04911c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -78,9 +78,9 @@
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
return;
}
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay*/);
+ if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+ null /* callback */, true /* withStartDelay*/);
}
onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
sendOnPipTransitionFinished(direction);
@@ -90,9 +90,9 @@
public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
- if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
- mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
- animator::clearContentOverlay, true /* withStartDelay */);
+ if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) {
+ mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
+ null /* callback */, true /* withStartDelay */);
}
sendOnPipTransitionCancelled(animator.getTransitionDirection());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index bc17ce9..118ad9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -383,6 +383,9 @@
}
@Override
- public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {}
+ public void attachAccessibilityOverlayToWindow(
+ SurfaceControl sc,
+ int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {}
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 1064867..63f20fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -797,21 +797,15 @@
mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
mPipBoundsState.getStashedState());
- // Scale PiP on density dpi change, so it appears to be the same size physically.
- final boolean densityDpiChanged =
- mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
- && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
- != layout.densityDpi());
- if (densityDpiChanged) {
- final float scale = (float) layout.densityDpi()
- / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
- postChangeBounds.set(0, 0,
- (int) (postChangeBounds.width() * scale),
- (int) (postChangeBounds.height() * scale));
- }
-
updateDisplayLayout.run();
+ // Resize the PiP bounds to be at the same scale relative to the new size spec. For
+ // example, if PiP was resized to 90% of the maximum size on the previous layout,
+ // make sure it is 90% of the new maximum size spec.
+ postChangeBounds.set(0, 0,
+ (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+ (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+
// Calculate the PiP bounds in the new orientation based on same fraction along the
// rotated movement bounds.
final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -822,6 +816,15 @@
mPipDisplayLayoutState.getDisplayBounds(),
mPipDisplayLayoutState.getDisplayLayout().stableInsets());
+ // make sure we user resize to the updated bounds to avoid animating to any outdated
+ // sizes from the previous layout upon double tap CUJ
+ mPipBoundsState.setHasUserResizedPip(true);
+ mTouchHandler.setUserResizeBounds(postChangeBounds);
+
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
// the app content in PiP mode may or may not be updated for the new density dpi.
@@ -833,15 +836,6 @@
// Directly move PiP to its final destination bounds without animation.
mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
}
-
- // if the pip window size is beyond allowed bounds user resize to normal bounds
- if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x
- || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x
- || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y
- || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) {
- mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction);
- }
-
} else {
updateDisplayLayout.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 8e3376f..f6cab48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -139,7 +139,7 @@
final InputChannel inputChannel = new InputChannel();
try {
// TODO(b/113087003): Support Picture-in-picture in multi-display.
- mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -163,7 +163,7 @@
}
try {
// TODO(b/113087003): Support Picture-in-picture in multi-display.
- mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to destroy input consumer, %s", TAG, e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index fc34772..63cef9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -107,7 +107,7 @@
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
- private static final float MENU_BACKGROUND_ALPHA = 0.3f;
+ private static final float MENU_BACKGROUND_ALPHA = 0.54f;
private static final float DISABLED_ACTION_ALPHA = 0.54f;
private int mMenuState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index e5f9fdc..f175775 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,7 +15,6 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
@@ -31,7 +30,6 @@
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Looper;
-import android.provider.DeviceConfig;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -155,21 +153,8 @@
mContext.getDisplay().getRealSize(mMaxSize);
reloadResources();
- mEnablePinchResize = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- PIP_PINCH_RESIZE,
- /* defaultValue = */ true);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mMainExecutor,
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
- mEnablePinchResize = properties.getBoolean(
- PIP_PINCH_RESIZE, /* defaultValue = */ true);
- }
- }
- });
+ final Resources res = mContext.getResources();
+ mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
}
public void onConfigurationChanged() {
@@ -579,6 +564,12 @@
resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
}
+ // If user resize is smaller than min size, auto resize to min
+ if (mLastResizeBounds.width() < mMinSize.x
+ || mLastResizeBounds.height() < mMinSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+ }
+
// get the current movement bounds
final Rect movementBounds = mPipBoundsAlgorithm
.getMovementBounds(mLastResizeBounds);
@@ -679,6 +670,8 @@
pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
+ pw.println(innerPrefix + "mMinSize=" + mMinSize);
+ pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
}
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 2ce4fb9..452a416 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -779,13 +779,10 @@
}
/**
- * Resizes the pip window and updates user resized bounds
- *
- * @param bounds target bounds to resize to
- * @param snapFraction snap fraction to apply after resizing
+ * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
*/
- void userResizeTo(Rect bounds, float snapFraction) {
- mPipResizeGestureHandler.userResizeTo(bounds, snapFraction);
+ void setUserResizeBounds(Rect bounds) {
+ mPipResizeGestureHandler.setUserResizeBounds(bounds);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
index 5f6b3fe..fc0b876 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -77,6 +77,19 @@
return mActionType;
}
+ static String getActionTypeString(@ActionType int actionType) {
+ switch (actionType) {
+ case ACTION_FULLSCREEN: return "ACTION_FULLSCREEN";
+ case ACTION_CLOSE: return "ACTION_CLOSE";
+ case ACTION_MOVE: return "ACTION_MOVE";
+ case ACTION_EXPAND_COLLAPSE: return "ACTION_EXPAND_COLLAPSE";
+ case ACTION_CUSTOM: return "ACTION_CUSTOM";
+ case ACTION_CUSTOM_CLOSE: return "ACTION_CUSTOM_CLOSE";
+ default:
+ return "UNDEFINED";
+ }
+ }
+
abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
abstract PendingIntent getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
index 4bba969..6b890c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -56,8 +56,10 @@
private final List<Listener> mListeners = new ArrayList<>();
private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
- private final List<TvPipAction> mActionsList;
+ private final List<TvPipAction> mActionsList = new ArrayList<>();
+ private final TvPipSystemAction mFullscreenAction;
private final TvPipSystemAction mDefaultCloseAction;
+ private final TvPipSystemAction mMoveAction;
private final TvPipSystemAction mExpandCollapseAction;
private final List<RemoteAction> mMediaActions = new ArrayList<>();
@@ -67,26 +69,27 @@
TvPipAction.SystemActionsHandler systemActionsHandler) {
mSystemActionsHandler = systemActionsHandler;
- mActionsList = new ArrayList<>();
- mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+ mFullscreenAction = new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
- mSystemActionsHandler));
-
+ mSystemActionsHandler);
mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
- mActionsList.add(mDefaultCloseAction);
-
- mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
- R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
-
+ mMoveAction = new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+ R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler);
mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
mSystemActionsHandler);
- mActionsList.add(mExpandCollapseAction);
+ initActions();
pipMediaController.addActionListener(this::onMediaActionsChanged);
}
+ private void initActions() {
+ mActionsList.add(mFullscreenAction);
+ mActionsList.add(mDefaultCloseAction);
+ mActionsList.add(mMoveAction);
+ }
+
@Override
public void executeAction(@TvPipAction.ActionType int actionType) {
if (mSystemActionsHandler != null) {
@@ -199,6 +202,14 @@
}
}
+ void reset() {
+ mActionsList.clear();
+ mMediaActions.clear();
+ mAppActions.clear();
+
+ initActions();
+ }
+
List<TvPipAction> getActionsList() {
return mActionsList;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index a48e969f..72c0cd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import java.util.Collections;
import java.util.Set;
/**
@@ -101,12 +102,29 @@
&& mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed();
if (isPipExpanded) {
- updateGravityOnExpansionToggled(/* expanding= */ true);
+ updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
+ @Override
+ public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getEntryDestinationBoundsIgnoringKeepClearAreas()", TAG);
+
+ updateExpandedPipSize();
+ final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
+ && !mTvPipBoundsState.isTvPipManuallyCollapsed();
+ if (isPipExpanded) {
+ updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded);
+ }
+ mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
+ return adjustBoundsForTemporaryDecor(getTvPipPlacement(Collections.emptySet(),
+ Collections.emptySet()).getUnstashedBounds());
+ }
+
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@Override
public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) {
@@ -133,16 +151,25 @@
*/
@NonNull
public Placement getTvPipPlacement() {
+ final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
+ final Set<Rect> unrestrictedKeepClearAreas =
+ mTvPipBoundsState.getUnrestrictedKeepClearAreas();
+
+ return getTvPipPlacement(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
+ }
+
+ /**
+ * Calculates the PiP bounds.
+ */
+ @NonNull
+ private Placement getTvPipPlacement(Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas) {
final Size pipSize = getPipSize();
final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
final Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
- final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
- final Set<Rect> unrestrictedKeepClearAreas =
- mTvPipBoundsState.getUnrestrictedKeepClearAreas();
-
mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity());
mKeepClearAlgorithm.setScreenSize(screenSize);
mKeepClearAlgorithm.setMovementBounds(insetBounds);
@@ -189,8 +216,11 @@
int updatedGravity;
if (expanding) {
- // Save collapsed gravity.
- mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity());
+ if (!mTvPipBoundsState.isTvPipExpanded()) {
+ // Save collapsed gravity.
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(
+ mTvPipBoundsState.getTvPipGravity());
+ }
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
updatedGravity = Gravity.CENTER_HORIZONTAL | currentY;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 47a8cc8..5ee3734e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -81,6 +81,7 @@
super(context, sizeSpecSource, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
+ mTvPipGravity = mDefaultGravity;
mPreviousCollapsedGravity = mDefaultGravity;
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
@@ -130,6 +131,7 @@
mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
mTvPipGravity = mDefaultGravity;
mPreviousCollapsedGravity = mDefaultGravity;
+ mIsTvPipExpanded = false;
mTvPipManuallyCollapsed = false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 46336ce..cd3d38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -56,6 +56,7 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -122,6 +123,7 @@
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
+ private final PipTransitionState mPipTransitionState;
private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
@@ -157,6 +159,7 @@
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
+ PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -177,6 +180,7 @@
pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
+ pipTransitionState,
pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
@@ -199,6 +203,7 @@
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
+ PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
@@ -212,6 +217,7 @@
Handler mainHandler,
ShellExecutor mainExecutor) {
mContext = context;
+ mPipTransitionState = pipTransitionState;
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mShellController = shellController;
@@ -365,7 +371,6 @@
"%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
- onPipDisappeared();
}
private void togglePipExpansion() {
@@ -420,6 +425,11 @@
@Override
public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
+ if (!mPipTransitionState.hasEnteredPip()) {
+ // Do not schedule a move animation while we're still transitioning into/out of PiP
+ return;
+ }
+
mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
animationDuration, null);
mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
@@ -447,7 +457,7 @@
return;
}
mPipTaskOrganizer.removePip();
- onPipDisappeared();
+ mTvPipMenuController.closeMenu();
}
@Override
@@ -477,7 +487,8 @@
mPipNotificationController.dismiss();
mActionBroadcastReceiver.unregister();
- mTvPipMenuController.closeMenu();
+ mTvPipMenuController.detach();
+ mTvPipActionsProvider.reset();
mTvPipBoundsState.resetTvPipState();
mTvPipBoundsController.reset();
setState(STATE_NO_PIP);
@@ -500,8 +511,6 @@
public void onPipTransitionCanceled(int direction) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
- mTvPipMenuController.onPipTransitionFinished(
- PipAnimationController.isInPipDirection(direction));
mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index b2a189b..c6803f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -62,13 +62,16 @@
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private TvPipBackgroundView mPipBackgroundView;
- private boolean mMenuIsFocused;
@TvPipMenuMode
private int mCurrentMenuMode = MODE_NO_MENU;
@TvPipMenuMode
private int mPrevMenuMode = MODE_NO_MENU;
+ /** When the window gains focus, enter this menu mode */
+ @TvPipMenuMode
+ private int mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
+
@IntDef(prefix = { "MODE_" }, value = {
MODE_NO_MENU,
MODE_MOVE_MENU,
@@ -170,6 +173,9 @@
mPipMenuView = createTvPipMenuView();
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+ mPipMenuView.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
+ onPipWindowFocusChanged(hasFocus);
+ });
}
@VisibleForTesting
@@ -224,13 +230,14 @@
void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementMenu()", TAG);
- switchToMenuMode(MODE_MOVE_MENU);
+ requestMenuMode(MODE_MOVE_MENU);
}
@Override
public void showMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
- switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
+ mPipMenuView.resetMenu();
+ requestMenuMode(MODE_ALL_ACTIONS_MENU);
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -250,13 +257,13 @@
void closeMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
- switchToMenuMode(MODE_NO_MENU);
+ requestMenuMode(MODE_NO_MENU);
}
@Override
public void detach() {
- closeMenu();
detachPipMenu();
+ switchToMenuMode(MODE_NO_MENU);
mLeash = null;
}
@@ -313,10 +320,21 @@
@Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
Rect pipBounds, float alpha) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha);
+ movePipMenu(pipTx, pipBounds, alpha);
+ }
- if (pipBounds.isEmpty()) {
+ /**
+ * Move the PiP menu with the given bounds and update its opacity.
+ * The PiP SurfaceControl is given if there is a need to synchronize the movements
+ * on the same frame as PiP.
+ */
+ public void movePipMenu(@Nullable SurfaceControl.Transaction pipTx, @Nullable Rect pipBounds,
+ float alpha) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipMenu: %s, alpha %s", TAG,
+ pipBounds != null ? pipBounds.toShortString() : null, alpha);
+
+ if ((pipBounds == null || pipBounds.isEmpty()) && alpha == ALPHA_NO_CHANGE) {
if (pipTx == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: no transaction given", TAG);
@@ -327,28 +345,36 @@
return;
}
- final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
- final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
- final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
if (pipTx == null) {
pipTx = new SurfaceControl.Transaction();
}
- pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
- pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+
+ final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+ final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+
+ if (pipBounds != null) {
+ final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
+ pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
+ pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+ updateMenuBounds(pipBounds);
+ }
if (alpha != ALPHA_NO_CHANGE) {
pipTx.setAlpha(frontSurface, alpha);
pipTx.setAlpha(backSurface, alpha);
}
- // Synchronize drawing the content in the front and back surfaces together with the pip
- // transaction and the position change for the front and back surfaces
- final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
- syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
- syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
- updateMenuBounds(pipBounds);
- syncGroup.addTransaction(pipTx);
- syncGroup.markSyncReady();
+ if (pipBounds != null) {
+ // Synchronize drawing the content in the front and back surfaces together with the pip
+ // transaction and the position change for the front and back surfaces
+ final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
+ syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
+ syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
+ syncGroup.addTransaction(pipTx);
+ syncGroup.markSyncReady();
+ } else {
+ pipTx.apply();
+ }
}
private boolean isMenuAttached() {
@@ -381,22 +407,31 @@
final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
- mSystemWindows.updateViewLayout(mPipBackgroundView,
- getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
- menuBounds.height()));
- mSystemWindows.updateViewLayout(mPipMenuView,
- getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
- menuBounds.height()));
- if (mPipMenuView != null) {
- mPipMenuView.setPipBounds(pipBounds);
+
+ boolean needsRelayout = mPipBackgroundView.getLayoutParams().width != menuBounds.width()
+ || mPipBackgroundView.getLayoutParams().height != menuBounds.height();
+ if (needsRelayout) {
+ mSystemWindows.updateViewLayout(mPipBackgroundView,
+ getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
+ mSystemWindows.updateViewLayout(mPipMenuView,
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
+ if (mPipMenuView != null) {
+ mPipMenuView.setPipBounds(pipBounds);
+ }
}
}
- // Start methods handling {@link TvPipMenuMode}
+ // Beginning of convenience methods for {@link TvPipMenuMode}
@VisibleForTesting
boolean isMenuOpen() {
- return mCurrentMenuMode != MODE_NO_MENU;
+ return isMenuOpen(mCurrentMenuMode);
+ }
+
+ private static boolean isMenuOpen(@TvPipMenuMode int menuMode) {
+ return menuMode != MODE_NO_MENU;
}
@VisibleForTesting
@@ -409,46 +444,6 @@
return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
}
- private void switchToMenuMode(@TvPipMenuMode int menuMode) {
- switchToMenuMode(menuMode, false);
- }
-
- private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
- // Note: we intentionally don't return early here, because the TvPipMenuView needs to
- // refresh the Ui even if there is no menu mode change.
- mPrevMenuMode = mCurrentMenuMode;
- mCurrentMenuMode = menuMode;
-
- ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
- getMenuModeString(), getMenuModeString(mPrevMenuMode));
-
- updateUiOnNewMenuModeRequest(resetMenu);
- updateDelegateOnNewMenuModeRequest();
- }
-
- private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
- if (mPipMenuView == null || mPipBackgroundView == null) return;
-
- mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
- mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
- mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
- grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
- }
-
- private void updateDelegateOnNewMenuModeRequest() {
- if (mPrevMenuMode == mCurrentMenuMode) return;
- if (mDelegate == null) return;
-
- if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
- mDelegate.onInMoveModeChanged();
- }
-
- if (mCurrentMenuMode == MODE_NO_MENU) {
- mDelegate.onMenuClosed();
- }
- }
-
@VisibleForTesting
String getMenuModeString() {
return getMenuModeString(mCurrentMenuMode);
@@ -467,6 +462,90 @@
}
}
+ // Beginning of methods handling switching between menu modes
+
+ private void requestMenuMode(@TvPipMenuMode int menuMode) {
+ if (isMenuOpen() == isMenuOpen(menuMode)) {
+ // No need to request a focus change. We can directly switch to the new mode.
+ switchToMenuMode(menuMode);
+ } else {
+ if (isMenuOpen(menuMode)) {
+ mMenuModeOnFocus = menuMode;
+ }
+
+ // Send a request to gain window focus if the menu is open, or lose window focus
+ // otherwise. Once the focus change happens, we will request the new mode in the
+ // callback {@link #onPipWindowFocusChanged}.
+ requestPipMenuFocus(isMenuOpen(menuMode));
+ }
+ // Note: we don't handle cases where there is a focus change currently in flight, because
+ // this is very unlikely to happen in practice and would complicate the logic.
+ }
+
+ private void requestPipMenuFocus(boolean focus) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: requestPipMenuFocus(%b)", TAG, focus);
+
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mSystemWindows.getFocusGrantToken(mPipMenuView), focus);
+ } catch (Exception e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus, %s", TAG, e);
+ }
+ }
+
+ /**
+ * Called when the menu window gains or loses focus.
+ */
+ @VisibleForTesting
+ void onPipWindowFocusChanged(boolean focused) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
+ switchToMenuMode(focused ? mMenuModeOnFocus : MODE_NO_MENU);
+
+ // Reset the default menu mode for focused state.
+ mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
+ }
+
+ /**
+ * Immediately switches to the menu mode in the given request. Updates the mDelegate and the UI.
+ * Doesn't handle any focus changes.
+ */
+ private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: switchToMenuMode: from=%s, to=%s", TAG, getMenuModeString(),
+ getMenuModeString(menuMode));
+
+ if (mCurrentMenuMode == menuMode) return;
+
+ mPrevMenuMode = mCurrentMenuMode;
+ mCurrentMenuMode = menuMode;
+ updateUiOnNewMenuModeRequest();
+ updateDelegateOnNewMenuModeRequest();
+ }
+
+ private void updateUiOnNewMenuModeRequest() {
+ if (mPipMenuView == null || mPipBackgroundView == null) return;
+
+ mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
+ mPipMenuView.transitionToMenuMode(mCurrentMenuMode);
+ mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
+ }
+
+ private void updateDelegateOnNewMenuModeRequest() {
+ if (mPrevMenuMode == mCurrentMenuMode) return;
+ if (mDelegate == null) return;
+
+ if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
+ mDelegate.onInMoveModeChanged();
+ }
+
+ if (!isMenuOpen()) {
+ mDelegate.onMenuClosed();
+ }
+ }
+
// Start {@link TvPipMenuView.Delegate} methods
@Override
@@ -476,42 +555,19 @@
}
@Override
- public void onBackPress() {
- if (!onExitMoveMode()) {
- closeMenu();
- }
- }
-
- @Override
- public boolean onExitMoveMode() {
+ public void onExitCurrentMenuMode() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
-
- final int saveMenuMode = mCurrentMenuMode;
- if (isInMoveMode()) {
- switchToMenuMode(mPrevMenuMode);
- }
- return saveMenuMode == MODE_MOVE_MENU;
+ "%s: onExitCurrentMenuMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+ requestMenuMode(isInMoveMode() ? mPrevMenuMode : MODE_NO_MENU);
}
@Override
- public boolean onPipMovement(int keycode) {
+ public void onPipMovement(int keycode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
if (isInMoveMode()) {
mDelegate.movePip(keycode);
}
- return isInMoveMode();
- }
-
- @Override
- public void onPipWindowFocusChanged(boolean focused) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
- mMenuIsFocused = focused;
- if (!focused && isMenuOpen()) {
- closeMenu();
- }
}
interface Delegate {
@@ -524,21 +580,6 @@
void closeEduText();
}
- private void grantPipMenuFocus(boolean grantFocus) {
- if (mMenuIsFocused == grantFocus) return;
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: grantWindowFocus(%b)", TAG, grantFocus);
-
- try {
- WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
- mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
- } catch (Exception e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Unable to update focus, %s", TAG, e);
- }
- }
-
private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
private final View mView;
private final int mZOrder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
index f86f987..202d36f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -168,6 +168,9 @@
* that the edu text will be marqueed
*/
private boolean isEduTextMarqueed() {
+ if (mEduTextView.getLayout() == null) {
+ return false;
+ }
final int availableWidth = (int) mEduTextView.getWidth()
- mEduTextView.getCompoundPaddingLeft()
- mEduTextView.getCompoundPaddingRight();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 45e1cde..57439a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -328,7 +328,7 @@
return menuUiBounds;
}
- void transitionToMenuMode(int menuMode, boolean resetMenu) {
+ void transitionToMenuMode(int menuMode) {
switch (menuMode) {
case MODE_NO_MENU:
hideAllUserControls();
@@ -337,7 +337,7 @@
showMoveMenu();
break;
case MODE_ALL_ACTIONS_MENU:
- showAllActionsMenu(resetMenu);
+ showAllActionsMenu();
break;
default:
throw new IllegalArgumentException(
@@ -362,13 +362,13 @@
mEduTextDrawer.closeIfNeeded();
}
- private void showAllActionsMenu(boolean resetMenu) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+ void resetMenu() {
+ scrollToFirstAction();
+ }
- if (resetMenu) {
- scrollToFirstAction();
- }
+ private void showAllActionsMenu() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showAllActionsMenu()", TAG);
if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
@@ -431,12 +431,6 @@
}
}
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- mListener.onPipWindowFocusChanged(hasWindowFocus);
- }
-
private void animateAlphaTo(float alpha, View view) {
if (view.getAlpha() == alpha) {
return;
@@ -483,28 +477,28 @@
if (event.getAction() == ACTION_UP) {
if (event.getKeyCode() == KEYCODE_BACK) {
- mListener.onBackPress();
+ mListener.onExitCurrentMenuMode();
return true;
}
- if (mA11yManager.isEnabled()) {
- return super.dispatchKeyEvent(event);
- }
-
- switch (event.getKeyCode()) {
- case KEYCODE_DPAD_UP:
- case KEYCODE_DPAD_DOWN:
- case KEYCODE_DPAD_LEFT:
- case KEYCODE_DPAD_RIGHT:
- return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
- event);
- case KEYCODE_ENTER:
- case KEYCODE_DPAD_CENTER:
- return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
- default:
- break;
+ if (mCurrentMenuMode == MODE_MOVE_MENU && !mA11yManager.isEnabled()) {
+ switch (event.getKeyCode()) {
+ case KEYCODE_DPAD_UP:
+ case KEYCODE_DPAD_DOWN:
+ case KEYCODE_DPAD_LEFT:
+ case KEYCODE_DPAD_RIGHT:
+ mListener.onPipMovement(event.getKeyCode());
+ return true;
+ case KEYCODE_ENTER:
+ case KEYCODE_DPAD_CENTER:
+ mListener.onExitCurrentMenuMode();
+ return true;
+ default:
+ // Dispatch key event as normal below
+ }
}
}
+
return super.dispatchKeyEvent(event);
}
@@ -529,7 +523,7 @@
if (a11yEnabled) {
mA11yDoneButton.setVisibility(VISIBLE);
mA11yDoneButton.setOnClickListener(v -> {
- mListener.onExitMoveMode();
+ mListener.onExitCurrentMenuMode();
});
mA11yDoneButton.requestFocus();
mA11yDoneButton.requestAccessibilityFocus();
@@ -626,26 +620,15 @@
interface Listener {
- void onBackPress();
+ /**
+ * Called when a button for exiting the current menu mode was pressed.
+ */
+ void onExitCurrentMenuMode();
/**
- * Called when a button for exiting move mode was pressed.
- *
- * @return true if the event was handled or false if the key event should be handled by the
- * next receiver.
+ * Called when a button to move the PiP in a certain direction, indicated by keycode.
*/
- boolean onExitMoveMode();
-
- /**
- * @return whether pip movement was handled.
- */
- boolean onPipMovement(int keycode);
-
- /**
- * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
- * has lost focus.
- */
- void onPipWindowFocusChanged(boolean focused);
+ void onPipMovement(int keycode);
/**
* The edu text closing impacts the size of the Picture-in-Picture window and influences
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index f315afb..21223c9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -35,7 +35,6 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -46,6 +45,7 @@
* TV specific changes to the PipTaskOrganizer.
*/
public class TvPipTaskOrganizer extends PipTaskOrganizer {
+ private final TvPipTransition mTvPipTransition;
public TvPipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@@ -56,7 +56,7 @@
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
- @NonNull PipTransitionController pipTransitionController,
+ @NonNull TvPipTransition tvPipTransition,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
@NonNull DisplayController displayController,
@@ -65,9 +65,10 @@
ShellExecutor mainExecutor) {
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
- surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
mainExecutor);
+ mTvPipTransition = tvPipTransition;
}
@Override
@@ -105,4 +106,14 @@
// when the menu alpha is 0 (e.g. when a fade-in animation starts).
return true;
}
+
+ @Override
+ protected void cancelAnimationOnTaskVanished() {
+ mTvPipTransition.cancelAnimations();
+ if (mLeash != null) {
+ mSurfaceControlTransactionFactory.getTransaction()
+ .setAlpha(mLeash, 0f)
+ .apply();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index f24b2b3..571c839 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -16,43 +16,822 @@
package com.android.wm.shell.pip.tv;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.transitTypeToString;
+
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP;
+import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+
+import android.animation.AnimationHandler;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.IBinder;
+import android.os.Trace;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
-import java.util.Optional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
/**
* PiP Transition for TV.
*/
-public class TvPipTransition extends PipTransition {
+public class TvPipTransition extends PipTransitionController {
+ private static final String TAG = "TvPipTransition";
+ private static final float ZOOM_ANIMATION_SCALE_FACTOR = 0.97f;
+
+ private final PipTransitionState mPipTransitionState;
+ private final PipAnimationController mPipAnimationController;
+ private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+ private final TvPipMenuController mTvPipMenuController;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory
+ mTransactionFactory;
+
+ private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+ ThreadLocal.withInitial(() -> {
+ AnimationHandler handler = new AnimationHandler();
+ handler.setProvider(new SfVsyncFrameCallbackProvider());
+ return handler;
+ });
+
+ private final long mEnterFadeOutDuration;
+ private final long mEnterFadeInDuration;
+ private final long mExitFadeOutDuration;
+ private final long mExitFadeInDuration;
+
+ @Nullable
+ private Animator mCurrentAnimator;
+
+ /**
+ * The Task window that is currently in PIP windowing mode.
+ */
+ @Nullable
+ private WindowContainerToken mCurrentPipTaskToken;
+
+ @Nullable
+ private IBinder mPendingExitTransition;
public TvPipTransition(Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
TvPipBoundsState tvPipBoundsState,
- PipDisplayLayoutState pipDisplayLayoutState,
- PipTransitionState pipTransitionState,
TvPipMenuController tvPipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipTransitionState pipTransitionState,
PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<SplitScreenController> splitScreenOptional) {
- super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
- pipDisplayLayoutState, pipTransitionState, tvPipMenuController,
- tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
- splitScreenOptional);
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ super(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, tvPipMenuController,
+ tvPipBoundsAlgorithm);
+ mPipTransitionState = pipTransitionState;
+ mPipAnimationController = pipAnimationController;
+ mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+ mTvPipMenuController = tvPipMenuController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+
+ mEnterFadeOutDuration = context.getResources().getInteger(
+ R.integer.config_tvPipEnterFadeOutDuration);
+ mEnterFadeInDuration = context.getResources().getInteger(
+ R.integer.config_tvPipEnterFadeInDuration);
+ mExitFadeOutDuration = context.getResources().getInteger(
+ R.integer.config_tvPipExitFadeOutDuration);
+ mExitFadeInDuration = context.getResources().getInteger(
+ R.integer.config_tvPipExitFadeInDuration);
}
+ @Override
+ public void startExitTransition(int type, WindowContainerTransaction out,
+ @Nullable Rect destinationBounds) {
+ cancelAnimations();
+ mPendingExitTransition = mTransitions.startTransition(type, out, this);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (isCloseTransition(info)) {
+ // PiP is closing (without reentering fullscreen activity)
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Starting close animation", TAG);
+ cancelAnimations();
+ startCloseAnimation(info, startTransaction, finishTransaction, finishCallback);
+ mCurrentPipTaskToken = null;
+ return true;
+
+ } else if (transition.equals(mPendingExitTransition)) {
+ // PiP is exiting (reentering fullscreen activity)
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Starting exit animation", TAG);
+
+ final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+ mPendingExitTransition = null;
+ // PipTaskChange can be null if the PIP task has been detached, for example, when the
+ // task contains multiple activities, the PIP will be moved to a new PIP task when
+ // entering, and be moved back when exiting. In that case, the PIP task will be removed
+ // immediately.
+ final TaskInfo pipTaskInfo = currentPipTaskChange != null
+ ? currentPipTaskChange.getTaskInfo()
+ : mPipOrganizer.getTaskInfo();
+ if (pipTaskInfo == null) {
+ throw new RuntimeException("Cannot find the pip task for exit-pip transition.");
+ }
+
+ final int type = info.getType();
+ switch (type) {
+ case TRANSIT_EXIT_PIP -> {
+ TransitionInfo.Change pipChange = currentPipTaskChange;
+ SurfaceControl activitySc = null;
+ if (mCurrentPipTaskToken == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
+ } else if (pipChange == null) {
+ // The pipTaskChange is null, this can happen if we are reparenting the
+ // PIP activity back to its original Task. In that case, we should animate
+ // the activity leash instead, which should be the change whose last parent
+ // is the recorded PiP Task.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mCurrentPipTaskToken.equals(change.getLastParent())) {
+ // Find the activity that is exiting PiP.
+ pipChange = change;
+ activitySc = change.getLeash();
+ break;
+ }
+ }
+ }
+ if (pipChange == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: No window of exiting PIP is found. Can't play expand "
+ + "animation",
+ TAG);
+ removePipImmediately(info, pipTaskInfo, startTransaction, finishTransaction,
+ finishCallback);
+ return true;
+ }
+ final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info);
+ final SurfaceControl pipLeash;
+ if (activitySc != null) {
+ // Use a local leash to animate activity in case the activity has
+ // letterbox which may be broken by PiP animation, e.g. always end at 0,0
+ // in parent and unable to include letterbox area in crop bounds.
+ final SurfaceControl activitySurface = pipChange.getLeash();
+ pipLeash = new SurfaceControl.Builder()
+ .setName(activitySc + "_pip-leash")
+ .setContainerLayer()
+ .setHidden(false)
+ .setParent(root.getLeash())
+ .build();
+ startTransaction.reparent(activitySurface, pipLeash);
+ // Put the activity at local position with offset in case it is letterboxed.
+ final Point activityOffset = pipChange.getEndRelOffset();
+ startTransaction.setPosition(activitySc, activityOffset.x,
+ activityOffset.y);
+ } else {
+ pipLeash = pipChange.getLeash();
+ startTransaction.reparent(pipLeash, root.getLeash());
+ }
+ startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
+ final Rect currentBounds = mPipBoundsState.getBounds();
+ final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
+ cancelAnimations();
+ startExitAnimation(pipTaskInfo, pipLeash, currentBounds, destinationBounds,
+ startTransaction,
+ finishTransaction, finishCallback);
+ }
+ // pass through here is intended
+ case TRANSIT_TO_BACK, TRANSIT_REMOVE_PIP -> removePipImmediately(info, pipTaskInfo,
+ startTransaction, finishTransaction,
+ finishCallback
+ );
+ default -> {
+ return false;
+ }
+ }
+ mCurrentPipTaskToken = null;
+ return true;
+
+ } else if (isEnteringPip(info)) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Starting enter animation", TAG);
+
+ // Search for an Enter PiP transition
+ TransitionInfo.Change enterPip = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ enterPip = change;
+ }
+ }
+ if (enterPip == null) {
+ throw new IllegalStateException("Trying to start PiP animation without a pip"
+ + "participant");
+ }
+
+ // Make sure other open changes are visible as entering PIP. Some may be hidden in
+ // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == enterPip) continue;
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.show(leash).setAlpha(leash, 1.f);
+ }
+ }
+
+ cancelAnimations();
+ startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task.
+ */
+ private void removePipImmediately(@NonNull TransitionInfo info,
+ @NonNull TaskInfo taskInfo, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: removePipImmediately", TAG);
+ cancelAnimations();
+ startTransaction.apply();
+ finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
+ mPipDisplayLayoutState.getDisplayBounds());
+ mTvPipMenuController.detach();
+ mPipOrganizer.onExitPipFinished(taskInfo);
+ finishCallback.onTransitionFinished(/* wct= */ null);
+
+ mPipTransitionState.setTransitionState(UNDEFINED);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK);
+ }
+
+ private void startCloseAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final TransitionInfo.Change pipTaskChange = findCurrentPipTaskChange(info);
+ final SurfaceControl pipLeash = pipTaskChange.getLeash();
+
+ final List<SurfaceControl> closeLeashes = new ArrayList<>();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (TransitionUtil.isClosingType(change.getMode()) && change != pipTaskChange) {
+ closeLeashes.add(change.getLeash());
+ }
+ }
+
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ mSurfaceTransactionHelper
+ .resetScale(startTransaction, pipLeash, pipBounds)
+ .crop(startTransaction, pipLeash, pipBounds)
+ .shadow(startTransaction, pipLeash, false);
+
+ final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction();
+ for (SurfaceControl leash : closeLeashes) {
+ startTransaction.setShadowRadius(leash, 0f);
+ }
+
+ ValueAnimator closeFadeOutAnimator = createAnimator();
+ closeFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+ closeFadeOutAnimator.setDuration(mExitFadeOutDuration);
+ closeFadeOutAnimator.addUpdateListener(
+ animationUpdateListener(pipLeash).fadingOut().withMenu());
+ for (SurfaceControl leash : closeLeashes) {
+ closeFadeOutAnimator.addUpdateListener(animationUpdateListener(leash).fadingOut());
+ }
+
+ closeFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(@NonNull Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close animation: start", TAG);
+ for (SurfaceControl leash : closeLeashes) {
+ startTransaction.setShadowRadius(leash, 0f);
+ }
+ startTransaction.apply();
+
+ mPipTransitionState.setTransitionState(EXITING_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_REMOVE_STACK);
+ }
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close animation: cancel", TAG);
+ sendOnPipTransitionCancelled(TRANSITION_DIRECTION_REMOVE_STACK);
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close animation: end", TAG);
+ mTvPipMenuController.detach();
+ finishCallback.onTransitionFinished(null /* wct */);
+ transaction.close();
+ mPipTransitionState.setTransitionState(UNDEFINED);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK);
+
+ mCurrentAnimator = null;
+ }
+ });
+
+ closeFadeOutAnimator.start();
+ mCurrentAnimator = closeFadeOutAnimator;
+ }
+
+ @Override
+ public void startEnterAnimation(@NonNull TransitionInfo.Change pipChange,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Keep track of the PIP task
+ mCurrentPipTaskToken = pipChange.getContainer();
+ final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
+ final SurfaceControl leash = pipChange.getLeash();
+
+ mTvPipMenuController.attach(leash);
+ setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
+ taskInfo.topActivityInfo);
+
+ final Rect pipBounds =
+ mPipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas();
+ mPipBoundsState.setBounds(pipBounds);
+ mTvPipMenuController.movePipMenu(null, pipBounds, 0f);
+
+ final WindowContainerTransaction resizePipWct = new WindowContainerTransaction();
+ resizePipWct.setWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED);
+ resizePipWct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED);
+ resizePipWct.setBounds(taskInfo.token, pipBounds);
+
+ mSurfaceTransactionHelper
+ .resetScale(finishTransaction, leash, pipBounds)
+ .crop(finishTransaction, leash, pipBounds)
+ .shadow(finishTransaction, leash, false);
+
+ final Rect currentBounds = pipChange.getStartAbsBounds();
+ final Rect fadeOutCurrentBounds = scaledRect(currentBounds, ZOOM_ANIMATION_SCALE_FACTOR);
+
+ final ValueAnimator enterFadeOutAnimator = createAnimator();
+ enterFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+ enterFadeOutAnimator.setDuration(mEnterFadeOutDuration);
+ enterFadeOutAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingOut()
+ .animateBounds(currentBounds, fadeOutCurrentBounds, currentBounds));
+
+ enterFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @SuppressLint("MissingPermission")
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter fade out animation: end", TAG);
+ SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper
+ .resetScale(tx, leash, pipBounds)
+ .crop(tx, leash, pipBounds)
+ .shadow(tx, leash, false);
+ mShellTaskOrganizer.applyTransaction(resizePipWct);
+ tx.apply();
+ }
+ });
+
+ final ValueAnimator enterFadeInAnimator = createAnimator();
+ enterFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER);
+ enterFadeInAnimator.setDuration(mEnterFadeInDuration);
+ enterFadeInAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingIn()
+ .withMenu()
+ .atBounds(pipBounds));
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet
+ .play(enterFadeInAnimator)
+ .after(500)
+ .after(enterFadeOutAnimator);
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter animation: start", TAG);
+ startTransaction.apply();
+ mPipTransitionState.setTransitionState(ENTERING_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter animation: cancel", TAG);
+ enterFadeInAnimator.setCurrentFraction(1f);
+ sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: enter animation: end", TAG);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.setBounds(taskInfo.token, pipBounds);
+ finishCallback.onTransitionFinished(wct);
+
+ mPipTransitionState.setTransitionState(ENTERED_PIP);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+ mCurrentAnimator = null;
+ }
+ });
+
+ animatorSet.start();
+ mCurrentAnimator = animatorSet;
+ }
+
+ private void startExitAnimation(@NonNull TaskInfo taskInfo, SurfaceControl leash,
+ Rect currentBounds, Rect destinationBounds,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final Rect fadeInStartBounds = scaledRect(destinationBounds, ZOOM_ANIMATION_SCALE_FACTOR);
+
+ final ValueAnimator exitFadeOutAnimator = createAnimator();
+ exitFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT);
+ exitFadeOutAnimator.setDuration(mExitFadeOutDuration);
+ exitFadeOutAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingOut()
+ .withMenu()
+ .atBounds(currentBounds));
+ exitFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit fade out animation: end", TAG);
+ startTransaction.apply();
+ mPipMenuController.detach();
+ }
+ });
+
+ final ValueAnimator exitFadeInAnimator = createAnimator();
+ exitFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER);
+ exitFadeInAnimator.setDuration(mExitFadeInDuration);
+ exitFadeInAnimator.addUpdateListener(
+ animationUpdateListener(leash)
+ .fadingIn()
+ .animateBounds(fadeInStartBounds, destinationBounds, destinationBounds));
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playSequentially(
+ exitFadeOutAnimator,
+ exitFadeInAnimator
+ );
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit animation: start", TAG);
+ mPipTransitionState.setTransitionState(EXITING_PIP);
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_LEAVE_PIP);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit animation: cancel", TAG);
+ sendOnPipTransitionCancelled(TRANSITION_DIRECTION_LEAVE_PIP);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exit animation: end", TAG);
+ mPipOrganizer.onExitPipFinished(taskInfo);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.setBounds(taskInfo.token, destinationBounds);
+ finishCallback.onTransitionFinished(wct);
+
+ mPipTransitionState.setTransitionState(UNDEFINED);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP);
+
+ mCurrentAnimator = null;
+ }
+ });
+
+ animatorSet.start();
+ mCurrentAnimator = animatorSet;
+ }
+
+ @NonNull
+ private ValueAnimator createAnimator() {
+ final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
+ return animator;
+ }
+
+ @NonNull
+ private TvPipTransitionAnimatorUpdateListener animationUpdateListener(
+ @NonNull SurfaceControl leash) {
+ return new TvPipTransitionAnimatorUpdateListener(leash, mTvPipMenuController,
+ mTransactionFactory.getTransaction(), mSurfaceTransactionHelper);
+ }
+
+ @NonNull
+ private static Rect scaledRect(@NonNull Rect rect, float scale) {
+ final Rect out = new Rect(rect);
+ out.inset((int) (rect.width() * (1 - scale) / 2), (int) (rect.height() * (1 - scale) / 2));
+ return out;
+ }
+
+ private boolean isCloseTransition(TransitionInfo info) {
+ final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
+ return currentPipTaskChange != null && info.getType() == TRANSIT_CLOSE;
+ }
+
+ @Nullable
+ private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) {
+ if (mCurrentPipTaskToken == null) {
+ return null;
+ }
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mCurrentPipTaskToken.equals(change.getContainer())) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Whether we should handle the given {@link TransitionInfo} animation as entering PIP.
+ */
+ private boolean isEnteringPip(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isEnteringPip(change, info.getType())) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Whether a particular change is a window that is entering pip.
+ */
+ @Override
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
+ && !Objects.equals(change.getContainer(), mCurrentPipTaskToken)) {
+ if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN
+ || transitType == TRANSIT_CHANGE) {
+ return true;
+ }
+ // Please file a bug to handle the unexpected transition type.
+ android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
+ + transitTypeToString(transitType), new Throwable());
+ }
+ return false;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG);
+ if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+ mCurrentAnimator.end();
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ if (requestHasPipEnter(request)) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: handle PiP enter request", TAG);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ augmentRequest(transition, request, wct);
+ return wct;
+ } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // if we receive a TRANSIT_TO_BACK type of request while in PiP
+ mPendingExitTransition = transition;
+
+ // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls
+ mPipTransitionState.setTransitionState(EXITING_PIP);
+
+ // return an empty WindowContainerTransaction so that we don't check other handlers
+ return new WindowContainerTransaction();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+ @NonNull WindowContainerTransaction outWCT) {
+ if (!requestHasPipEnter(request)) {
+ throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
+ }
+ outWCT.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED);
+ }
+
+ /**
+ * Cancel any ongoing PiP transitions/animations.
+ */
+ public void cancelAnimations() {
+ if (mPipAnimationController.isAnimating()) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ mPipAnimationController.resetAnimatorState();
+ }
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.cancel();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.end();
+ }
+ }
+
+ private static class TvPipTransitionAnimatorUpdateListener implements
+ ValueAnimator.AnimatorUpdateListener {
+ private final SurfaceControl mLeash;
+ private final TvPipMenuController mTvPipMenuController;
+ private final SurfaceControl.Transaction mTransaction;
+ private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+ private final RectF mTmpRectF = new RectF();
+ private final Rect mTmpRect = new Rect();
+
+ private float mStartAlpha = ALPHA_NO_CHANGE;
+ private float mEndAlpha = ALPHA_NO_CHANGE;
+
+ @Nullable
+ private Rect mStartBounds;
+ @Nullable
+ private Rect mEndBounds;
+ private Rect mWindowContainerBounds;
+ private boolean mShowMenu;
+
+ TvPipTransitionAnimatorUpdateListener(@NonNull SurfaceControl leash,
+ @NonNull TvPipMenuController tvPipMenuController,
+ @NonNull SurfaceControl.Transaction transaction,
+ @NonNull PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+ mLeash = leash;
+ mTvPipMenuController = tvPipMenuController;
+ mTransaction = transaction;
+ mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+ }
+
+ public TvPipTransitionAnimatorUpdateListener animateAlpha(
+ @FloatRange(from = 0.0, to = 1.0) float startAlpha,
+ @FloatRange(from = 0.0, to = 1.0) float endAlpha) {
+ mStartAlpha = startAlpha;
+ mEndAlpha = endAlpha;
+ return this;
+ }
+
+ public TvPipTransitionAnimatorUpdateListener animateBounds(@NonNull Rect startBounds,
+ @NonNull Rect endBounds, @NonNull Rect windowContainerBounds) {
+ mStartBounds = startBounds;
+ mEndBounds = endBounds;
+ mWindowContainerBounds = windowContainerBounds;
+ return this;
+ }
+
+ public TvPipTransitionAnimatorUpdateListener atBounds(@NonNull Rect bounds) {
+ return animateBounds(bounds, bounds, bounds);
+ }
+
+ public TvPipTransitionAnimatorUpdateListener fadingOut() {
+ return animateAlpha(1f, 0f);
+ }
+
+ public TvPipTransitionAnimatorUpdateListener fadingIn() {
+ return animateAlpha(0f, 1f);
+ }
+
+ public TvPipTransitionAnimatorUpdateListener withMenu() {
+ mShowMenu = true;
+ return this;
+ }
+
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final float fraction = animation.getAnimatedFraction();
+ final float alpha = lerp(mStartAlpha, mEndAlpha, fraction);
+ if (mStartBounds != null && mEndBounds != null) {
+ lerp(mStartBounds, mEndBounds, fraction, mTmpRectF);
+ applyAnimatedValue(alpha, mTmpRectF);
+ } else {
+ applyAnimatedValue(alpha, null);
+ }
+ }
+
+ private void applyAnimatedValue(float alpha, @Nullable RectF bounds) {
+ Trace.beginSection("applyAnimatedValue");
+ final SurfaceControl.Transaction tx = mTransaction;
+
+ Trace.beginSection("leash scale and alpha");
+ if (alpha != ALPHA_NO_CHANGE) {
+ mSurfaceTransactionHelper.alpha(tx, mLeash, alpha);
+ }
+ if (bounds != null) {
+ mSurfaceTransactionHelper.scale(tx, mLeash, mWindowContainerBounds, bounds);
+ }
+ mSurfaceTransactionHelper.shadow(tx, mLeash, false);
+ tx.show(mLeash);
+ Trace.endSection();
+
+ if (mShowMenu) {
+ Trace.beginSection("movePipMenu");
+ if (bounds != null) {
+ mTmpRect.set((int) bounds.left, (int) bounds.top, (int) bounds.right,
+ (int) bounds.bottom);
+ mTvPipMenuController.movePipMenu(tx, mTmpRect, alpha);
+ } else {
+ mTvPipMenuController.movePipMenu(tx, null, alpha);
+ }
+ Trace.endSection();
+ } else {
+ mTvPipMenuController.movePipMenu(tx, null, 0f);
+ }
+
+ tx.apply();
+ Trace.endSection();
+ }
+
+ private float lerp(float start, float end, float fraction) {
+ return start * (1 - fraction) + end * fraction;
+ }
+
+ private void lerp(@NonNull Rect start, @NonNull Rect end, float fraction,
+ @NonNull RectF out) {
+ out.set(
+ start.left * (1 - fraction) + end.left * fraction,
+ start.top * (1 - fraction) + end.top * fraction,
+ start.right * (1 - fraction) + end.right * fraction,
+ start.bottom * (1 - fraction) + end.bottom * fraction);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
index ec09827..6dabb3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -1,3 +1,4 @@
# WM shell sub-module pip owner
hwwang@google.com
mateuszc@google.com
+gabiyev@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
deleted file mode 100644
index b8e4c04..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 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.pip2;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipMenuController;
-import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-/** Placeholder, for demonstrate purpose only. */
-public class PipTransition extends PipTransitionController {
- public PipTransition(
- @NonNull ShellInit shellInit,
- @NonNull ShellTaskOrganizer shellTaskOrganizer,
- @NonNull Transitions transitions,
- PipBoundsState pipBoundsState,
- PipMenuController pipMenuController,
- PipBoundsAlgorithm pipBoundsAlgorithm) {
- super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- pipBoundsAlgorithm);
- }
-
- @Override
- protected void onInit() {
- if (PipUtils.isPip2ExperimentEnabled()) {
- mTransitions.addHandler(this);
- }
- }
-
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- return null;
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- return false;
- }
-
- @Override
- public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {}
-
- @Override
- public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
- @Nullable SurfaceControl.Transaction finishT) {}
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final String TAG = PipController.class.getSimpleName();
+
+ private Context mContext;
+ private ShellController mShellController;
+ private DisplayController mDisplayController;
+ private DisplayInsetsController mDisplayInsetsController;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
+ private PipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ mContext = context;
+ mShellController = shellController;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ // Ensure that we have the display info in case we get calls to update the bounds before the
+ // listener calls back
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+
+ mShellController.addConfigurationChangeListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ onDisplayChanged(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+ }
+ });
+ }
+
+ /**
+ * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+ */
+ public static PipController create(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Device doesn't support Pip feature", TAG);
+ return null;
+ }
+ return new PipController(context, shellInit, shellController, displayController,
+ displayInsetsController, pipDisplayLayoutState);
+ }
+
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ mPipDisplayLayoutState.onConfigurationChanged();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ private void onDisplayChanged(DisplayLayout layout) {
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
new file mode 100644
index 0000000..0448d94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 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.pip2.phone;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.PipTransitionController;
+
+/**
+ * Scheduler for Shell initiated PiP transitions and animations.
+ */
+public class PipScheduler {
+ private static final String TAG = PipScheduler.class.getSimpleName();
+ private static final String BROADCAST_FILTER = PipScheduler.class.getCanonicalName();
+
+ private final Context mContext;
+ private final PipBoundsState mPipBoundsState;
+ private final ShellExecutor mMainExecutor;
+ private PipSchedulerReceiver mSchedulerReceiver;
+ private PipTransitionController mPipTransitionController;
+
+ // pinned PiP task's WC token
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
+
+ // pinned PiP task's leash
+ @Nullable
+ private SurfaceControl mPinnedTaskLeash;
+
+ /**
+ * A temporary broadcast receiver to initiate exit PiP via expand.
+ * This will later be modified to be triggered by the PiP menu.
+ */
+ private class PipSchedulerReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ scheduleExitPipViaExpand();
+ }
+ }
+
+ public PipScheduler(Context context, PipBoundsState pipBoundsState,
+ ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMainExecutor = mainExecutor;
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ // temporary broadcast receiver to initiate exit PiP via expand
+ mSchedulerReceiver = new PipSchedulerReceiver();
+ ContextCompat.registerReceiver(mContext, mSchedulerReceiver,
+ new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED);
+ }
+ }
+
+ void setPipTransitionController(PipTransitionController pipTransitionController) {
+ mPipTransitionController = pipTransitionController;
+ }
+
+ void setPinnedTaskLeash(SurfaceControl pinnedTaskLeash) {
+ mPinnedTaskLeash = pinnedTaskLeash;
+ }
+
+ void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
+ mPipTaskToken = pipTaskToken;
+ }
+
+ @Nullable
+ private WindowContainerTransaction getExitPipViaExpandTransaction() {
+ if (mPipTaskToken == null || mPinnedTaskLeash == null) {
+ return null;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ // final expanded bounds to be inherited from the parent
+ wct.setBounds(mPipTaskToken, null);
+ // if we are hitting a multi-activity case
+ // windowing mode change will reparent to original host task
+ wct.setWindowingMode(mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ return wct;
+ }
+
+ /**
+ * Schedules exit PiP via expand transition.
+ */
+ public void scheduleExitPipViaExpand() {
+ WindowContainerTransaction wct = getExitPipViaExpandTransaction();
+ if (wct != null) {
+ mMainExecutor.execute(() -> {
+ mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
+ null /* destinationBounds */);
+ });
+ }
+ }
+
+ void onExitPip() {
+ mPipTaskToken = null;
+ mPinnedTaskLeash = null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
new file mode 100644
index 0000000..6200ea5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 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.pip2.phone;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Implementation of transitions for PiP on phone.
+ */
+public class PipTransition extends PipTransitionController {
+ private static final String TAG = PipTransition.class.getSimpleName();
+
+ private final PipScheduler mPipScheduler;
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
+ @Nullable
+ private IBinder mAutoEnterButtonNavTransition;
+ @Nullable
+ private IBinder mExitViaExpandTransition;
+
+ public PipTransition(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
+ PipBoundsState pipBoundsState,
+ PipMenuController pipMenuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipScheduler pipScheduler) {
+ super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
+ pipBoundsAlgorithm);
+
+ mPipScheduler = pipScheduler;
+ mPipScheduler.setPipTransitionController(this);
+ }
+
+ @Override
+ protected void onInit() {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Override
+ public void startExitTransition(int type, WindowContainerTransaction out,
+ @android.annotation.Nullable Rect destinationBounds) {
+ if (out == null) {
+ return;
+ }
+ IBinder transition = mTransitions.startTransition(type, out, this);
+ if (type == TRANSIT_EXIT_PIP) {
+ mExitViaExpandTransition = transition;
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ if (isAutoEnterInButtonNavigation(request)) {
+ mAutoEnterButtonNavTransition = transition;
+ return getEnterPipTransaction(transition, request);
+ }
+ return null;
+ }
+
+ @Override
+ public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+ @NonNull WindowContainerTransaction outWct) {
+ if (isAutoEnterInButtonNavigation(request)) {
+ outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
+ mAutoEnterButtonNavTransition = transition;
+ }
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {}
+
+ private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ // cache the original task token to check for multi-activity case later
+ final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+ PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+ mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
+ pipParams, mPipBoundsAlgorithm);
+
+ // calculate the entry bounds and notify core to move task to pinned with final bounds
+ final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ mPipBoundsState.setBounds(entryBounds);
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+ return wct;
+ }
+
+ private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
+ final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+ if (pipTask == null) {
+ return false;
+ }
+ if (pipTask.pictureInPictureParams == null) {
+ return false;
+ }
+
+ // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
+ // implies that we are entering PiP in button navigation mode. This is guaranteed by
+ // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
+ return requestInfo.getType() == TRANSIT_OPEN
+ && pipTask.pictureInPictureParams.isAutoEnterEnabled();
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition == mAutoEnterButtonNavTransition) {
+ mAutoEnterButtonNavTransition = null;
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+ mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ } else if (transition == mExitViaExpandTransition) {
+ mExitViaExpandTransition = null;
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
+ }
+ return false;
+ }
+
+ @Nullable
+ private TransitionInfo.Change getPipChange(TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ private void onExitPip() {
+ mPipTaskToken = null;
+ mPipScheduler.onExitPip();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 069066e..2616b8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -34,4 +34,10 @@
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
}
+
+ /**
+ * Adds the listener to be notified of whether the recent task animation is running.
+ */
+ default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 63ca6a5..e421356 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -203,6 +203,17 @@
}
}
+ @Nullable
+ public SplitBounds getSplitBoundsForTaskId(int taskId) {
+ if (taskId == INVALID_TASK_ID) {
+ return null;
+ }
+
+ // We could do extra verification of requiring both taskIds of a pair and verifying that
+ // the same split bounds object is returned... but meh. Seems unnecessary.
+ return mTaskSplitBoundsMap.get(taskId);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -329,7 +340,7 @@
continue;
}
- if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent()
+ if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
freeformTasks.add(taskInfo);
@@ -417,6 +428,21 @@
executor.execute(() -> callback.accept(tasks));
});
}
+
+ @Override
+ public void addAnimationStateListener(Executor executor, Consumer<Boolean> listener) {
+ mMainExecutor.execute(() -> {
+ if (mTransitionHandler == null) {
+ return;
+ }
+ mTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
+ @Override
+ public void onAnimationStateChanged(boolean running) {
+ executor.execute(() -> listener.accept(running));
+ }
+ });
+ });
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 50ba897..d023cea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.recents;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -23,6 +24,8 @@
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -51,14 +54,17 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Handles the Recents (overview) animation. Only one of these can run at a time. A recents
@@ -69,8 +75,11 @@
private final Transitions mTransitions;
private final ShellExecutor mExecutor;
+ @Nullable
+ private final RecentTasksController mRecentTasksController;
private IApplicationThread mAnimApp = null;
private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+ private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>();
/**
* List of other handlers which might need to mix recents with other things. These are checked
@@ -78,10 +87,15 @@
*/
private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+ private final HomeTransitionObserver mHomeTransitionObserver;
+
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
- @Nullable RecentTasksController recentTasksController) {
+ @Nullable RecentTasksController recentTasksController,
+ HomeTransitionObserver homeTransitionObserver) {
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
+ mRecentTasksController = recentTasksController;
+ mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
shellInit.addInitCallback(() -> {
@@ -100,6 +114,11 @@
mMixers.remove(mixer);
}
+ /** Adds the callback for receiving the state change of transition. */
+ public void addTransitionStateListener(RecentsTransitionStateListener listener) {
+ mStateListeners.add(listener);
+ }
+
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -112,18 +131,21 @@
wct.sendPendingIntent(intent, fillIn, options);
final RecentsController controller = new RecentsController(listener);
RecentsMixedHandler mixer = null;
- Transitions.TransitionHandler mixedHandler = null;
+ Consumer<IBinder> setTransitionForMixer = null;
for (int i = 0; i < mMixers.size(); ++i) {
- mixedHandler = mMixers.get(i).handleRecentsRequest(wct);
- if (mixedHandler != null) {
+ setTransitionForMixer = mMixers.get(i).handleRecentsRequest(wct);
+ if (setTransitionForMixer != null) {
mixer = mMixers.get(i);
break;
}
}
final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
- mixedHandler == null ? this : mixedHandler);
+ mixer == null ? this : mixer);
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onTransitionStarted(transition);
+ }
if (mixer != null) {
- mixer.setRecentsTransition(transition);
+ setTransitionForMixer.accept(transition);
}
if (transition != null) {
controller.setTransition(transition);
@@ -165,13 +187,14 @@
return false;
}
final RecentsController controller = mControllers.get(controllerIdx);
- Transitions.setRunningRemoteTransitionDelegate(mAnimApp);
+ final IApplicationThread animApp = mAnimApp;
mAnimApp = null;
if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startAnimation: failed to start animation");
return false;
}
+ Transitions.setRunningRemoteTransitionDelegate(animApp);
return true;
}
@@ -232,6 +255,7 @@
private ArrayList<TaskState> mOpeningTasks = null;
private WindowContainerToken mPipTask = null;
+ private int mPipTaskId = -1;
private WindowContainerToken mRecentsTask = null;
private int mRecentsTaskId = -1;
private TransitionInfo mInfo = null;
@@ -263,7 +287,7 @@
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finish(mWillFinishToHome, false /* leaveHint */);
+ finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -297,7 +321,7 @@
}
}
if (mFinishCB != null) {
- finishInner(toHome, false /* userLeave */);
+ finishInner(toHome, false /* userLeave */, null /* finishCb */);
} else {
cleanUp();
}
@@ -382,6 +406,9 @@
mTransition = null;
mPendingPauseSnapshotsForCancel = null;
mControllers.remove(this);
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(false);
+ }
}
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
@@ -426,6 +453,7 @@
mLeashMap = new ArrayMap<>();
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+ int closingSplitTaskId = INVALID_TASK_ID;
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
@@ -452,6 +480,7 @@
apps.add(target);
if (TransitionUtil.isClosingType(change.getMode())) {
mPausingTasks.add(new TaskState(change, target.leash));
+ closingSplitTaskId = change.getTaskInfo().taskId;
if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" adding pausing leaf home taskId=%d", taskInfo.taskId);
@@ -509,13 +538,19 @@
}
}
t.apply();
+ Bundle b = new Bundle(1 /*capacity*/);
+ b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS,
+ mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId));
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.start: calling onAnimationStart", mInstanceId);
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
- new Rect(0, 0, 0, 0), new Rect());
+ new Rect(0, 0, 0, 0), new Rect(), b);
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(true);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Error starting recents animation", e);
cancel("onAnimationStart() failed");
@@ -556,6 +591,13 @@
cancel("transit_sleep");
return;
}
+ if (mKeyguardLocked || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.merge: keyguard is locked", mInstanceId);
+ // We will not accept new changes if we are swiping over the keyguard.
+ cancel(true /* toHome */, false /* withScreenshots */, "keyguard_locked");
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge", mInstanceId);
// Keep all tasks in one list because order matters.
@@ -586,7 +628,8 @@
&& mRecentsTask.equals(change.getContainer());
hasTaskChange = hasTaskChange || isRootTask;
final boolean isLeafTask = leafTaskFilter.test(change);
- if (TransitionUtil.isOpeningType(change.getMode())) {
+ if (TransitionUtil.isOpeningType(change.getMode())
+ || TransitionUtil.isOrderOnly(change)) {
if (isRecentsTask) {
recentsOpening = change;
} else if (isRootTask || isLeafTask) {
@@ -643,7 +686,8 @@
// now and let it do its animation (since recents is going to be occluded).
sendCancelWithSnapshots();
mExecutor.executeDelayed(
- () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0);
+ () -> finishInner(true /* toHome */, false /* userLeaveHint */,
+ null /* finishCb */), 0);
return;
}
if (recentsOpening != null) {
@@ -779,8 +823,8 @@
} else if (!didMergeThings) {
// Didn't recognize anything in incoming transition so don't merge it.
Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing="
- + foundRecentsClosing);
- if (foundRecentsClosing) {
+ + foundRecentsClosing + " recentsTaskId=" + mRecentsTaskId);
+ if (foundRecentsClosing || mRecentsTaskId < 0) {
mWillFinishToHome = false;
cancel(false /* toHome */, false /* withScreenshots */, "didn't merge");
}
@@ -861,26 +905,34 @@
@Override
public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "[%d] RecentsController.setFinishTaskTransaction: taskId=%d",
- mInstanceId, taskId);
mExecutor.execute(() -> {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.setFinishTaskTransaction: taskId=%d,"
+ + " [mFinishCB is non-null]=%b",
+ mInstanceId, taskId, mFinishCB != null);
if (mFinishCB == null) return;
mPipTransaction = finishTransaction;
+ mPipTaskId = taskId;
});
}
@Override
@SuppressLint("NewApi")
- public void finish(boolean toHome, boolean sendUserLeaveHint) {
- mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint));
+ public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
}
- private void finishInner(boolean toHome, boolean sendUserLeaveHint) {
+ private void finishInner(boolean toHome, boolean sendUserLeaveHint,
+ IResultReceiver runnerFinishCb) {
if (mFinishCB == null) {
Slog.e(TAG, "Duplicate call to finish");
return;
}
+ if (!toHome) {
+ // For some transitions, we may have notified home activity that it became visible.
+ // We need to notify the observer that we are no longer going home.
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
+ "willFinishToHome=%b state=%d",
@@ -895,19 +947,8 @@
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome
- // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
- // live tile (pausing app). If the gesture is "cancelled" we need to return to
- // 3p launcher instead of "task-switching" away from it.
- && (!mWillFinishToHome || mPausingSeparateHome)
- && mPausingTasks != null && mState == STATE_NORMAL) {
- if (mPausingSeparateHome) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to 3p home");
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to app");
- }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -939,33 +980,85 @@
wct.restoreTransientOrder(mRecentsTask);
}
} else {
+ if (mPausingSeparateHome) {
+ if (mOpeningTasks.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " recents occluded 3p home");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " switch task by recents on 3p home");
+ }
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
// The general case: committing to recents, going home, or switching tasks.
for (int i = 0; i < mOpeningTasks.size(); ++i) {
t.show(mOpeningTasks.get(i).mTaskSurface);
}
for (int i = 0; i < mPausingTasks.size(); ++i) {
- if (!sendUserLeaveHint && mPausingTasks.get(i).isLeaf()) {
- // This means recents is not *actually* finishing, so of course we gotta
- // do special stuff in WMCore to accommodate.
- wct.setDoNotPip(mPausingTasks.get(i).mToken);
- }
- // Since we will reparent out of the leashes, pre-emptively hide the child
- // surface to match the leash. Otherwise, there will be a flicker before the
- // visibility gets committed in Core when using split-screen (in splitscreen,
- // the leaf-tasks are not "independent" so aren't hidden by normal setup).
- t.hide(mPausingTasks.get(i).mTaskSurface);
+ cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint);
}
- if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
- t.show(mInfo.getChange(mPipTask).getLeash());
- PictureInPictureSurfaceTransaction.apply(mPipTransaction,
- mInfo.getChange(mPipTask).getLeash(), t);
+ for (int i = 0; i < mClosingTasks.size(); ++i) {
+ cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
+ }
+ if (mPipTransaction != null && sendUserLeaveHint) {
+ SurfaceControl pipLeash = null;
+ if (mPipTask != null) {
+ pipLeash = mInfo.getChange(mPipTask).getLeash();
+ } else if (mPipTaskId != -1) {
+ // find a task with taskId from #setFinishTaskTransaction()
+ for (TransitionInfo.Change change : mInfo.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().taskId == mPipTaskId) {
+ pipLeash = change.getLeash();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner:"
+ + " found a change with taskId=%d", mPipTaskId);
+ }
+ }
+ }
+ if (pipLeash == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner: no valid PiP leash;"
+ + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
+ mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ } else {
+ t.show(pipLeash);
+ PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsController.finishInner: PiP transaction %s merged",
+ mPipTransaction);
+ }
+ mPipTaskId = -1;
mPipTask = null;
mPipTransaction = null;
}
}
cleanUp();
finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
+ if (runnerFinishCb != null) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: calling finish callback",
+ mInstanceId);
+ runnerFinishCb.send(0, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report transition finished", e);
+ }
+ }
+ }
+
+ private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
+ SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
+ if (!sendUserLeaveHint && task.isLeaf()) {
+ // This means recents is not *actually* finishing, so of course we gotta
+ // do special stuff in WMCore to accommodate.
+ wct.setDoNotPip(task.mToken);
+ }
+ // Since we will reparent out of the leashes, pre-emptively hide the child
+ // surface to match the leash. Otherwise, there will be a flicker before the
+ // visibility gets committed in Core when using split-screen (in splitscreen,
+ // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+ finishTransaction.hide(task.mTaskSurface);
}
@Override
@@ -1057,22 +1150,17 @@
* An interface for a mixed handler to receive information about recents requests (since these
* come into this handler directly vs from WMCore request).
*/
- public interface RecentsMixedHandler {
+ public interface RecentsMixedHandler extends Transitions.TransitionHandler {
/**
* Called when a recents request comes in. The handler can add operations to outWCT. If
- * the handler wants to "accept" the transition, it should return itself; otherwise, it
- * should return `null`.
+ * the handler wants to "accept" the transition, it should return a Consumer accepting the
+ * IBinder for the transition. If not, it should return `null`.
*
* If a mixed-handler accepts this recents, it will be the de-facto handler for this
* transition and is required to call the associated {@link #startAnimation},
* {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
*/
- Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT);
-
- /**
- * Reports the transition token associated with the accepted recents request. If there was
- * a problem starting the request, this will be called with `null`.
- */
- void setRecentsTransition(@Nullable IBinder transition);
+ @Nullable
+ Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
new file mode 100644
index 0000000..e8733eb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.recents;
+
+import android.os.IBinder;
+
+/** The listener for the events from {@link RecentsTransitionHandler}. */
+public interface RecentsTransitionStateListener {
+
+ /** Notifies whether the recents animation is running. */
+ default void onAnimationStateChanged(boolean running) {
+ }
+
+ /** Notifies that a recents shell transition has started. */
+ default void onTransitionStarted(IBinder transition) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 14304a3..253acc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -91,42 +91,42 @@
* Starts tasks simultaneously in one transition.
*/
oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteTransition remoteTransition,
+ int splitPosition, int snapPosition, in RemoteTransition remoteTransition,
in InstanceId instanceId) = 10;
/**
* Starts a pair of intent and task in one transition.
*/
oneway void startIntentAndTask(in PendingIntent pendingIntent, int userId1, in Bundle options1,
- int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ int taskId, in Bundle options2, int sidePosition, int snapPosition,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
* Starts a pair of shortcut and task in one transition.
*/
oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
- in Bundle options2, int splitPosition, float splitRatio,
+ in Bundle options2, int splitPosition, int snapPosition,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
*/
oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
- in Bundle options2, int splitPosition, float splitRatio,
+ in Bundle options2, int splitPosition, int snapPosition,
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, int userId1,
- in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition,
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
/**
* Starts a pair of shortcut and task using legacy transition system.
*/
oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo,
- in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition,
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
/**
@@ -135,7 +135,7 @@
oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1, int userId1,
in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
- float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
+ int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
/**
* Start a pair of intents in one transition.
@@ -143,7 +143,7 @@
oneway void startIntents(in PendingIntent pendingIntent1, int userId1,
in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
- float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+ int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index f90ee58..7b57097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -25,6 +25,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -43,6 +44,7 @@
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.TaskInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
@@ -85,6 +87,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -130,6 +133,7 @@
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
public static final int EXIT_REASON_RECREATE_SPLIT = 10;
public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
+ public static final int EXIT_REASON_ENTER_DESKTOP = 12;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -143,6 +147,7 @@
EXIT_REASON_CHILD_TASK_ENTER_PIP,
EXIT_REASON_RECREATE_SPLIT,
EXIT_REASON_FULLSCREEN_SHORTCUT,
+ EXIT_REASON_ENTER_DESKTOP
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -362,6 +367,14 @@
return mStageCoordinator.getStageOfTask(taskId);
}
+ /**
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
+ */
+ public boolean isLeftRightSplit() {
+ return mStageCoordinator.isLeftRightSplit();
+ }
+
/** Check split is foreground and task is under split or not by taskId. */
public boolean isTaskInSplitScreenForeground(int taskId) {
return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
@@ -598,8 +611,8 @@
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (options1 == null) options1 = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
@@ -623,13 +636,14 @@
}
mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
- activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter,
+ activityOptions.toBundle(), taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
}
void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
if (options1 == null) options1 = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
final String packageName1 = shortcutInfo.getPackage();
@@ -656,7 +670,7 @@
}
}
mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ options2, splitPosition, snapPosition, remoteTransition, instanceId);
}
/**
@@ -671,8 +685,8 @@
private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
@@ -693,12 +707,12 @@
}
}
mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
- options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ options1, taskId, options2, splitPosition, snapPosition, adapter, instanceId);
}
private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
@@ -706,10 +720,10 @@
// recents that hasn't launched and is not being organized
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+ boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
- fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
if (mRecentTasksOptional.isPresent()) {
@@ -724,15 +738,20 @@
Toast.LENGTH_SHORT).show();
}
}
+ if (options2 != null) {
+ Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
+ fillInIntent = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
+ }
mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ options2, splitPosition, snapPosition, remoteTransition, instanceId);
}
private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -756,14 +775,15 @@
}
mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
- splitPosition, splitRatio, adapter, instanceId);
+ splitPosition, snapPosition, adapter, instanceId);
}
private void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -772,12 +792,12 @@
? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
final ActivityOptions activityOptions2 = options2 != null
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
+ boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- fillInIntent2 = new Intent();
- fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ setSecondIntentMultipleTask = true;
if (shortcutInfo1 != null) {
activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
@@ -796,9 +816,13 @@
Toast.LENGTH_SHORT).show();
}
}
+ if (options2 != null) {
+ Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
+ fillInIntent2 = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
+ }
mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
- activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition,
+ activityOptions2.toBundle(), splitPosition, snapPosition, remoteTransition,
instanceId);
}
@@ -814,21 +838,25 @@
final String packageName1 = SplitScreenUtils.getPackageName(intent);
final String packageName2 = getPackageName(reverseSplitPosition(position));
final int userId2 = getUserId(reverseSplitPosition(position));
+ final ComponentName component = intent.getIntent().getComponent();
+
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background. If we don't explicitly reuse, new may be created even if the app
+ // isn't multi-instance because WM won't automatically remove/reuse the previous instance
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .orElse(null);
+ if (taskInfo != null) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mStageCoordinator.startTask(taskInfo.taskId, position, options);
+ } else {
+ startTask(taskInfo.taskId, position, options);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
+ return;
+ }
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
- // To prevent accumulating large number of instances in the background, reuse task
- // in the background with priority.
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(
- intent.getIntent().getComponent(), userId1))
- .orElse(null);
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Start task in background");
- return;
- }
-
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -900,6 +928,34 @@
return false;
}
+ /**
+ * Determines whether the widgetIntent needs to be modified if multiple tasks of its
+ * corresponding package/app are supported. There are 4 possible paths:
+ * <li> We select a widget for second app which is the same as the first app </li>
+ * <li> We select a widget for second app which is different from the first app </li>
+ * <li> No widgets involved, we select a second app that is the same as first app </li>
+ * <li> No widgets involved, we select a second app that is different from the first app
+ * (returns null) </li>
+ *
+ * @return an {@link Intent} with the appropriate {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+ * added on or not depending on {@param launchMultipleTasks}.
+ */
+ @Nullable
+ private Intent resolveWidgetFillinIntent(@Nullable Intent widgetIntent,
+ boolean launchMultipleTasks) {
+ Intent fillInIntent2 = null;
+ if (launchMultipleTasks && widgetIntent != null) {
+ fillInIntent2 = widgetIntent;
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ } else if (widgetIntent != null) {
+ fillInIntent2 = widgetIntent;
+ } else if (launchMultipleTasks) {
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ return fillInIntent2;
+ }
+
RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
if (ENABLE_SHELL_TRANSITIONS) return null;
@@ -1009,6 +1065,8 @@
return "CHILD_TASK_ENTER_PIP";
case EXIT_REASON_RECREATE_SPLIT:
return "RECREATE_SPLIT";
+ case EXIT_REASON_ENTER_DESKTOP:
+ return "ENTER_DESKTOP";
default:
return "unknown reason, reason int = " + exitReason;
}
@@ -1217,78 +1275,82 @@
@Override
public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- taskId1, options1, taskId2, options2, splitPosition,
- splitRatio, adapter, instanceId));
+ taskId1, options1, taskId2, options2, splitPosition, snapPosition,
+ adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
- Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
- userId1, options1, taskId, options2, splitPosition, splitRatio,
- adapter, instanceId));
+ userId1, options1, taskId, options2, splitPosition,
+ snapPosition, adapter, instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.startShortcutAndTaskWithLegacyTransition(
shortcutInfo, options1, taskId, options2, splitPosition,
- splitRatio, adapter, instanceId));
+ snapPosition, adapter, instanceId));
}
@Override
public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
- taskId2, options2, splitPosition, splitRatio, remoteTransition,
+ taskId2, options2, splitPosition, snapPosition, remoteTransition,
instanceId));
}
@Override
public void startIntentAndTask(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
(controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1,
- taskId, options2, splitPosition, splitRatio, remoteTransition,
+ taskId, options2, splitPosition, snapPosition, remoteTransition,
instanceId));
}
@Override
public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, @Nullable RemoteTransition remoteTransition,
- InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
(controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId));
+ options2, splitPosition, snapPosition, remoteTransition, instanceId));
}
@Override
public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
(controller) ->
controller.startIntentsWithLegacyTransition(pendingIntent1, userId1,
shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2,
- options2, splitPosition, splitRatio, adapter, instanceId)
+ options2, splitPosition, snapPosition, adapter, instanceId)
);
}
@@ -1296,13 +1358,14 @@
public void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntents",
(controller) ->
controller.startIntents(pendingIntent1, userId1, shortcutInfo1,
options1, pendingIntent2, userId2, shortcutInfo2, options2,
- splitPosition, splitRatio, remoteTransition, instanceId)
+ splitPosition, snapPosition, remoteTransition, instanceId)
);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 5483fa5..f4ab226 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -25,6 +25,7 @@
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
@@ -42,6 +43,7 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -192,6 +194,8 @@
return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
case EXIT_REASON_FULLSCREEN_SHORTCUT:
return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+ case EXIT_REASON_ENTER_DESKTOP:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c95c9f0..96e57e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -129,6 +129,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitWindowManager;
@@ -143,6 +144,8 @@
import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -220,6 +223,7 @@
private boolean mExitSplitScreenOnHide;
private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
+ private boolean mSkipEvictingMainStageChildren;
private boolean mIsExiting;
private boolean mIsRootTranslucent;
@VisibleForTesting
@@ -260,6 +264,10 @@
mStartIntent2 = startIntent2;
mActivatePosition = position;
}
+ SplitRequest(int taskId1, int position) {
+ mActivateTaskId = taskId1;
+ mActivatePosition = position;
+ }
SplitRequest(int taskId1, int taskId2, int position) {
mActivateTaskId = taskId1;
mActivateTaskId2 = taskId2;
@@ -461,6 +469,7 @@
}
// Due to drag already pip task entering split by this method so need to reset flag here.
mIsDropEntering = false;
+ mSkipEvictingMainStageChildren = false;
return true;
}
@@ -553,6 +562,36 @@
}
}
+ /** Use this method to launch an existing Task via a taskId */
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ mSplitRequest = new SplitRequest(taskId, position);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ wct.startTask(taskId, options);
+ // If this should be mixed, send the task to avoid split handle transition directly.
+ if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+ mTaskOrganizer.applyTransaction(wct);
+ return;
+ }
+
+ // Don't evict the main stage children as this can race and happen after the activity is
+ // started into that stage
+ if (!isSplitScreenVisible()) {
+ mSkipEvictingMainStageChildren = true;
+ // Starting the split task without evicting children will bring the single root task
+ // container forward, so ensure that we hide the divider before we start animate it
+ setDividerVisibility(false, null);
+ }
+
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int extraTransitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+
+ mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
+ extraTransitType, !mIsDropEntering);
+ }
+
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
@@ -572,6 +611,15 @@
return;
}
+ // Don't evict the main stage children as this can race and happen after the activity is
+ // started into that stage
+ if (!isSplitScreenVisible()) {
+ mSkipEvictingMainStageChildren = true;
+ // Starting the split task without evicting children will bring the single root task
+ // container forward, so ensure that we hide the divider before we start animate it
+ setDividerVisibility(false, null);
+ }
+
// If split screen is not activated, we're expecting to open a pair of apps to split.
final int extraTransitType = mMainStage.isActive()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -639,8 +687,8 @@
}
/** Starts 2 tasks in one transition. */
- void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
@@ -662,13 +710,13 @@
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
- startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId);
+ startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId);
}
/** Start an intent and a task to a split pair in one transition. */
void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
@@ -684,13 +732,14 @@
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
}
/** Starts a shortcut and a task to a split pair in one transition. */
void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
@@ -705,7 +754,7 @@
addActivityOptions(options1, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
}
/**
@@ -716,14 +765,14 @@
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
- @Nullable Bundle mainOptions, float splitRatio,
+ @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
if (!mMainStage.isActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
mMainStage.activate(wct, false /* reparent */);
}
- mSplitLayout.setDivideRatio(splitRatio);
+ mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
@@ -750,7 +799,7 @@
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, Intent fillInIntent2,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
@@ -772,7 +821,7 @@
}
setSideStagePosition(splitPosition, wct);
- mSplitLayout.setDivideRatio(splitRatio);
+ mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
@@ -802,7 +851,8 @@
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId2 == INVALID_TASK_ID) {
@@ -823,7 +873,7 @@
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition);
- startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId2, options2, splitPosition, snapPosition, adapter,
instanceId);
}
@@ -832,8 +882,8 @@
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
@Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
@@ -852,13 +902,13 @@
pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition);
}
startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
- splitPosition, splitRatio, adapter, instanceId);
+ splitPosition, snapPosition, adapter, instanceId);
}
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
@@ -871,15 +921,15 @@
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition);
- startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
}
/** Starts a pair of shortcut and task using legacy transition. */
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
@@ -890,7 +940,7 @@
addActivityOptions(options1, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
}
@@ -940,18 +990,19 @@
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
- mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId);
}
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
- splitRatio, adapter, instanceId);
+ snapPosition, adapter, instanceId);
}
/**
@@ -962,15 +1013,15 @@
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (!isSplitScreenVisible()) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
- mSplitLayout.setDivideRatio(splitRatio);
+ mSplitLayout.setDivideRatio(snapPosition);
// Apply surface bounds before animation start.
SurfaceControl.Transaction startT = mTransactionPool.acquire();
@@ -1295,7 +1346,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1587,7 +1638,9 @@
// Ensure to evict old splitting tasks because the new split pair might be composed by
// one of the splitting tasks, evicting the task when finishing entering transition
// won't guarantee to put the task to the indicated new position.
- mMainStage.evictAllChildren(wct);
+ if (!mSkipEvictingMainStageChildren) {
+ mMainStage.evictAllChildren(wct);
+ }
mMainStage.reparentTopTask(wct);
prepareSplitLayout(wct, resizeAnim);
}
@@ -1633,7 +1686,7 @@
}
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
- mSplitLayout.update(finishT);
+ mSplitLayout.update(finishT, true /* resetImePosition */);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
@@ -1647,13 +1700,14 @@
finishT.show(mRootTaskLeash);
setSplitsVisible(true);
mIsDropEntering = false;
+ mSkipEvictingMainStageChildren = false;
mSplitRequest = null;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
}
@@ -1743,10 +1797,10 @@
}
if (stage == STAGE_TYPE_MAIN) {
mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
} else {
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
if (present) {
updateRecentTasksSplitPair();
@@ -1778,7 +1832,7 @@
rightBottomTaskId = sideStageTopTaskId;
}
SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
- leftTopTaskId, rightBottomTaskId);
+ leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
@@ -1827,9 +1881,10 @@
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
&& mMainStage.isActive()) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
- // the new rotation config.
+ // the new rotation config. Don't reset the IME state since those updates are not in
+ // sync with task info changes.
mIsDividerRemoteAnimating = false;
- mSplitLayout.update(null /* t */);
+ mSplitLayout.update(null /* t */, false /* resetImePosition */);
onLayoutSizeChanged(mSplitLayout);
}
}
@@ -1895,6 +1950,7 @@
if (mIsDropEntering) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
mIsDropEntering = false;
+ mSkipEvictingMainStageChildren = false;
} else {
mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
@@ -2089,6 +2145,7 @@
if (mIsDropEntering) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
mIsDropEntering = false;
+ mSkipEvictingMainStageChildren = false;
} else {
mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
@@ -2107,7 +2164,7 @@
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
}
}
@@ -2199,8 +2256,12 @@
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
- private boolean isLandscape() {
- return mSplitLayout.isLandscape();
+ /**
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
+ */
+ boolean isLeftRightSplit() {
+ return mSplitLayout != null && mSplitLayout.isLeftRightSplit();
}
/**
@@ -2244,6 +2305,25 @@
return SPLIT_POSITION_UNDEFINED;
}
+ /**
+ * Returns the {@link StageType} where {@param token} is being used
+ * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise
+ */
+ @StageType
+ public int getSplitItemStage(@Nullable WindowContainerToken token) {
+ if (token == null) {
+ return STAGE_TYPE_UNDEFINED;
+ }
+
+ if (mMainStage.containsToken(token)) {
+ return STAGE_TYPE_MAIN;
+ } else if (mSideStage.containsToken(token)) {
+ return STAGE_TYPE_SIDE;
+ }
+
+ return STAGE_TYPE_UNDEFINED;
+ }
+
@Override
public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
final StageTaskListener topLeftStage =
@@ -2269,7 +2349,7 @@
*/
public void updateSurfaces(SurfaceControl.Transaction transaction) {
updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
- mSplitLayout.update(transaction);
+ mSplitLayout.update(transaction, true /* resetImePosition */);
}
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -2491,7 +2571,16 @@
mRecentTasks.ifPresent(
recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
}
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
+ @StageType int topStage = STAGE_TYPE_UNDEFINED;
+ if (isSplitScreenVisible()) {
+ // Get the stage where a child exists to keep that stage onTop
+ if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) {
+ topStage = STAGE_TYPE_MAIN;
+ } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) {
+ topStage = STAGE_TYPE_SIDE;
+ }
+ }
+ prepareExitSplitScreen(topStage, outWCT);
}
}
@@ -2533,7 +2622,9 @@
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
- mSplitLayout.update(startTransaction);
+ // Don't reset the IME state since those updates are not in sync with the
+ // display change transition
+ mSplitLayout.update(startTransaction, false /* resetImePosition */);
}
if (mMixedHandler.isEnteringPip(change, transitType)) {
@@ -2634,7 +2725,7 @@
startTransaction, finishTransaction, finishCallback)) {
if (mSplitTransitions.isPendingResize(transition)) {
// Only need to update in resize because divider exist before transition.
- mSplitLayout.update(startTransaction);
+ mSplitLayout.update(startTransaction, true /* resetImePosition */);
startTransaction.apply();
}
return true;
@@ -2705,7 +2796,7 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
- shouldAnimate = startPendingEnterAnimation(
+ shouldAnimate = startPendingEnterAnimation(transition,
mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
@@ -2744,7 +2835,7 @@
}
}
- private boolean startPendingEnterAnimation(
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
@NonNull SplitScreenTransitions.EnterSession enterTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
@@ -2773,21 +2864,22 @@
}
}
- if (mSplitTransitions.mPendingEnter.mExtraTransitType
+ SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter;
+ if (pendingEnter.mExtraTransitType
== TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
// Open to side should only be used when split already active and foregorund.
if (mainChild == null && sideChild == null) {
Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
"Launched a task in split, but didn't receive any task in transition."));
// This should happen when the target app is already on front, so just cancel.
- mSplitTransitions.mPendingEnter.cancel(null);
+ pendingEnter.cancel(null);
return true;
}
} else {
if (mainChild == null || sideChild == null) {
final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
- mSplitTransitions.mPendingEnter.cancel(
+ pendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
"launched 2 tasks in split, but didn't receive "
@@ -2798,6 +2890,12 @@
if (mRecentTasks.isPresent() && sideChild != null) {
mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
}
+ if (pendingEnter.mRemoteHandler != null) {
+ // Pass false for aborted since WM didn't abort, business logic chose to
+ // terminate/exit early
+ pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
+ false /*aborted*/, finishT);
+ }
mSplitUnsupportedToast.show();
return true;
}
@@ -2908,7 +3006,11 @@
return SPLIT_POSITION_UNDEFINED;
}
- /** Synchronize split-screen state with transition and make appropriate preparations. */
+ /**
+ * Synchronize split-screen state with transition and make appropriate preparations.
+ * @param toStage The stage that will not be dismissed. If set to
+ * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed
+ */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
@@ -3123,6 +3225,7 @@
null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
}
+ @NeverCompile
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
@@ -3131,6 +3234,7 @@
pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
+ pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
pw.println(innerPrefix + "MainStage");
pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3142,10 +3246,7 @@
mSideStage.dump(pw, childPrefix);
pw.println(innerPrefix + "SideStageListener");
mSideStageListener.dump(pw, childPrefix);
- if (mMainStage.isActive()) {
- pw.println(innerPrefix + "SplitLayout");
- mSplitLayout.dump(pw, childPrefix);
- }
+ mSplitLayout.dump(pw, childPrefix);
if (!mPausingTasks.isEmpty()) {
pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
}
@@ -3167,6 +3268,7 @@
public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
if (!isSplitScreenVisible()) {
mIsDropEntering = true;
+ mSkipEvictingMainStageChildren = true;
}
if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
// If split running background, exit split first.
@@ -3197,7 +3299,7 @@
mLogger.logExit(exitReason,
SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
/**
@@ -3210,7 +3312,7 @@
toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
!toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
!toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 20da877..edb5aba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -46,6 +46,8 @@
private final int mIconFadeOutDuration;
private final int mAppRevealDelay;
private final int mAppRevealDuration;
+ @SplashScreenExitAnimationUtils.ExitAnimationType
+ private final int mAnimationType;
private final int mAnimationDuration;
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
@@ -91,6 +93,8 @@
}
mAppRevealDuration = context.getResources().getInteger(
R.integer.starting_window_app_reveal_anim_duration);
+ mAnimationType = context.getResources().getInteger(
+ R.integer.starting_window_exit_animation_type);
mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration);
mMainWindowShiftLength = mainWindowShiftLength;
mFinishCallback = handleFinish;
@@ -98,10 +102,10 @@
}
void startAnimations() {
- SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
- mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
- mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this, mRoundedCornerRadius);
+ SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView,
+ mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame,
+ mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha,
+ mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index a7e4385..e86b62d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -20,6 +20,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
@@ -54,6 +55,7 @@
public class SplashScreenExitAnimationUtils {
private static final boolean DEBUG_EXIT_ANIMATION = false;
private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+ private static final boolean DEBUG_EXIT_FADE_ANIMATION = false;
private static final String TAG = "SplashScreenExitAnimationUtils";
private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
@@ -62,20 +64,47 @@
private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
/**
+ * This splash screen exit animation type uses a radial vanish to hide
+ * the starting window and slides up the main window content.
+ * @hide
+ */
+ public static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
+
+ /**
+ * This splash screen exit animation type fades out the starting window
+ * to reveal the main window content.
+ * @hide
+ */
+ public static final int TYPE_FADE_OUT = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_RADIAL_VANISH_SLIDE_UP,
+ TYPE_FADE_OUT,
+ })
+ public @interface ExitAnimationType {}
+
+ /**
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
* window with rounded corner radius.
*/
- static void startAnimations(ViewGroup splashScreenView,
- SurfaceControl firstWindowSurface, int mainWindowShiftLength,
- TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
- int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
- float roundedCornerRadius) {
- ValueAnimator animator =
- createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
- transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
- iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener, roundedCornerRadius);
+ static void startAnimations(@ExitAnimationType int animationType,
+ ViewGroup splashScreenView, SurfaceControl firstWindowSurface,
+ int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame,
+ int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+ float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+ Animator.AnimatorListener animatorListener, float roundedCornerRadius) {
+ ValueAnimator animator;
+ if (animationType == TYPE_FADE_OUT) {
+ animator = createFadeOutAnimation(splashScreenView, animationDuration,
+ iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay,
+ appRevealDuration, animatorListener);
+ } else {
+ animator = createRadialVanishSlideUpAnimator(splashScreenView,
+ firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+ animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+ appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
+ }
animator.start();
}
@@ -89,17 +118,18 @@
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
- startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
- transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
- iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener, 0f /* roundedCornerRadius */);
+ // Start the default 'reveal' animation.
+ startAnimations(TYPE_RADIAL_VANISH_SLIDE_UP, splashScreenView,
+ firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+ animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+ appRevealDelay, appRevealDuration, animatorListener, 0f /* roundedCornerRadius */);
}
/**
* Creates the animator to fade out the icon, reveal the app, and shift up main window.
* @hide
*/
- private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+ private static ValueAnimator createRadialVanishSlideUpAnimator(ViewGroup splashScreenView,
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
@@ -210,6 +240,59 @@
return nightMode == Configuration.UI_MODE_NIGHT_YES;
}
+ private static ValueAnimator createFadeOutAnimation(ViewGroup splashScreenView,
+ int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+ float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+ Animator.AnimatorListener animatorListener) {
+
+ if (DEBUG_EXIT_FADE_ANIMATION) {
+ splashScreenView.setBackgroundColor(Color.BLUE);
+ }
+
+ final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(animationDuration);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.addUpdateListener(animation -> {
+
+ float linearProgress = (float) animation.getAnimatedValue();
+
+ // Icon fade out progress (always starts immediately)
+ final float iconFadeProgress = ICON_INTERPOLATOR.getInterpolation(getProgress(
+ linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+ View iconView = null;
+ View brandingView = null;
+
+ if (splashScreenView instanceof SplashScreenView) {
+ iconView = ((SplashScreenView) splashScreenView).getIconView();
+ brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+ }
+ if (iconView != null) {
+ iconView.setAlpha(iconStartAlpha * (1f - iconFadeProgress));
+ }
+ if (brandingView != null) {
+ brandingView.setAlpha(brandingStartAlpha * (1f - iconFadeProgress));
+ }
+
+ // Splash screen fade out progress (possibly delayed)
+ final float splashFadeProgress = Interpolators.ALPHA_OUT.getInterpolation(
+ getProgress(linearProgress, appRevealDelay,
+ appRevealDuration, animationDuration));
+
+ splashScreenView.setAlpha(1f - splashFadeProgress);
+
+ if (DEBUG_EXIT_FADE_ANIMATION) {
+ Slog.d(TAG, "progress -> animation: " + linearProgress
+ + "\t icon alpha: " + ((iconView != null) ? iconView.getAlpha() : "n/a")
+ + "\t splash alpha: " + splashScreenView.getAlpha()
+ );
+ }
+ });
+ if (animatorListener != null) {
+ animator.addListener(animatorListener);
+ }
+ return animator;
+ }
+
/**
* View which creates a circular reveal of the underlying view.
* @hide
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 29be343..ae21c4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -23,6 +23,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_APP_PREFERS_ICON;
import android.annotation.ColorInt;
import android.annotation.IntDef;
@@ -112,32 +113,20 @@
*/
static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
- // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
- // icon which it's non-transparent foreground area is similar to it's background area, then
- // do not enlarge the foreground drawable.
- // For example, an icon with the foreground 108*108 opaque pixels and it's background
- // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
- private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
-
- /**
- * If the developer doesn't specify a background for the icon, we slightly scale it up.
- *
- * The background is either manually specified in the theme or the Adaptive Icon
- * background is used if it's different from the window background.
- */
- private static final float NO_BACKGROUND_SCALE = 192f / 160;
private final Context mContext;
private final HighResIconProvider mHighResIconProvider;
-
private int mIconSize;
private int mDefaultIconSize;
private int mBrandingImageWidth;
private int mBrandingImageHeight;
private int mMainWindowShiftLength;
+ private float mEnlargeForegroundIconThreshold;
+ private float mNoBackgroundScale;
private int mLastPackageContextConfigHash;
private final TransactionPool mTransactionPool;
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
private final Handler mSplashscreenWorkerHandler;
+ private final boolean mCanUseAppIconForSplashScreen;
@VisibleForTesting
final ColorCache mColorCache;
@@ -154,6 +143,8 @@
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
+ mCanUseAppIconForSplashScreen = context.getResources().getBoolean(
+ com.android.wm.shell.R.bool.config_canUseAppIconForSplashScreen);
}
/**
@@ -256,7 +247,7 @@
}
params.layoutInDisplayCutoutMode = a.getInt(
R.styleable.Window_windowLayoutInDisplayCutoutMode,
- params.layoutInDisplayCutoutMode);
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
a.recycle();
@@ -336,6 +327,10 @@
com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
+ mEnlargeForegroundIconThreshold = mContext.getResources().getFloat(
+ com.android.wm.shell.R.dimen.splash_icon_enlarge_foreground_threshold);
+ mNoBackgroundScale = mContext.getResources().getFloat(
+ com.android.wm.shell.R.dimen.splash_icon_no_background_scale_factor);
}
/**
@@ -436,7 +431,10 @@
getWindowAttrs(context, mTmpAttrs);
mLastPackageContextConfigHash = context.getResources().getConfiguration().hashCode();
- final Drawable legacyDrawable = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ final @StartingWindowType int splashType =
+ suggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN && !canUseIcon(info)
+ ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN : suggestType;
+ final Drawable legacyDrawable = splashType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
? peekLegacySplashscreenContent(context, mTmpAttrs) : null;
final ActivityInfo ai = info.targetActivityInfo != null
? info.targetActivityInfo
@@ -448,12 +446,17 @@
return new SplashViewBuilder(context, ai)
.setWindowBGColor(themeBGColor)
.overlayDrawable(legacyDrawable)
- .chooseStyle(suggestType)
+ .chooseStyle(splashType)
.setUiThreadInitConsumer(uiThreadInitConsumer)
.setAllowHandleSolidColor(info.allowHandleSolidColorSplashScreen())
.build();
}
+ private boolean canUseIcon(StartingWindowInfo info) {
+ return mCanUseAppIconForSplashScreen || mTmpAttrs.mSplashScreenIcon != null
+ || (info.startingWindowTypeParameter & TYPE_PARAMETER_APP_PREFERS_ICON) != 0;
+ }
+
private int getBGColorFromCache(ActivityInfo ai, IntSupplier windowBgColorSupplier) {
return mColorCache.getWindowColor(ai.packageName, mLastPackageContextConfigHash,
mTmpAttrs.mWindowBgColor, mTmpAttrs.mWindowBgResId, windowBgColorSupplier).mBgColor;
@@ -604,14 +607,14 @@
// There is no background below the icon, so scale the icon up
if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
|| mTmpAttrs.mIconBgColor == mThemeColor) {
- mFinalIconSize *= NO_BACKGROUND_SCALE;
+ mFinalIconSize *= mNoBackgroundScale;
}
createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
} else {
final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
final int scaledIconDpi =
- (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
+ (int) (0.5f + iconScale * densityDpi * mNoBackgroundScale);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
iconDrawable = mHighResIconProvider.getIcon(
mActivityInfo, densityDpi, scaledIconDpi);
@@ -693,8 +696,8 @@
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// scale by 192/160 if we only draw adaptiveIcon's foreground.
final float noBgScale =
- iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
- ? NO_BACKGROUND_SCALE : 1f;
+ iconColor.mFgNonTranslucentRatio < mEnlargeForegroundIconThreshold
+ ? mNoBackgroundScale : 1f;
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
// current settings.
mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index c2f15f6..e6418f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -182,7 +182,7 @@
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
- mSession.remove(mWindow);
+ mSession.remove(mWindow.asBinder());
} catch (RemoteException e) {
// nothing
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 0d77a2e..35a1fa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -29,12 +29,16 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewTreeObserver;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.concurrent.Executor;
/**
@@ -74,6 +78,7 @@
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
private Insets mCaptionInsets;
+ private Handler mHandler;
public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -81,6 +86,7 @@
// TODO(b/266736992): Think about a better way to set the TaskViewBase on the
// TaskViewTaskController and vice-versa
mTaskViewTaskController.setTaskViewBase(this);
+ mHandler = Handler.getMain();
getHolder().addCallback(this);
}
@@ -117,14 +123,16 @@
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
onLocationChanged();
if (taskInfo.taskDescription != null) {
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+ runOnViewThread(() -> setResizeBackgroundColor(bgColor));
}
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo.taskDescription != null) {
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+ runOnViewThread(() -> setResizeBackgroundColor(bgColor));
}
}
@@ -143,7 +151,14 @@
@Override
public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
- setResizeBackgroundColor(t, bgColor);
+ if (mHandler.getLooper().isCurrentThread()) {
+ // We can only use the transaction if it can updated synchronously, otherwise the tx
+ // will be applied immediately after but also used/updated on the view thread which
+ // will lead to a race and/or crash
+ runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
+ } else {
+ runOnViewThread(() -> setResizeBackgroundColor(bgColor));
+ }
}
/**
@@ -272,12 +287,14 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mHandler = getHandler();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mHandler = Handler.getMain();
}
/** Returns the task info for the task in the TaskView. */
@@ -285,4 +302,24 @@
public ActivityManager.RunningTaskInfo getTaskInfo() {
return mTaskViewTaskController.getTaskInfo();
}
+
+ /**
+ * Sets the handler, only for testing.
+ */
+ @VisibleForTesting
+ void setHandler(Handler viewHandler) {
+ mHandler = viewHandler;
+ }
+
+ /**
+ * Ensures that the given runnable runs on the view's thread.
+ */
+ private void runOnViewThread(Runnable r) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ r.run();
+ } else {
+ // If this call is not from the same thread as the view, then post it
+ mHandler.post(r);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index cefbb17..34c015f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -165,19 +165,6 @@
return null;
}
- /**
- * Returns all the pending transitions for a given `taskView`.
- * @param taskView the pending transition should be for this.
- */
- ArrayList<PendingTransition> findAllPending(TaskViewTaskController taskView) {
- ArrayList<PendingTransition> list = new ArrayList<>();
- for (int i = mPending.size() - 1; i >= 0; --i) {
- if (mPending.get(i).mTaskView != taskView) continue;
- list.add(mPending.get(i));
- }
- return list;
- }
-
private PendingTransition findPending(IBinder claimed) {
for (int i = 0; i < mPending.size(); ++i) {
if (mPending.get(i).mClaimed != claimed) continue;
@@ -273,10 +260,9 @@
// Task view isn't visible, the bounds will next visibility update.
return;
}
- PendingTransition pendingOpen = findPendingOpeningTransition(taskView);
- if (pendingOpen != null) {
- // There is already an opening transition in-flight, the window bounds will be
- // set in prepareOpenAnimation (via the window crop) if needed.
+ if (hasPending()) {
+ // There is already a transition in-flight, the window bounds will be set in
+ // prepareOpenAnimation.
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -344,6 +330,11 @@
continue;
}
if (isHide) {
+ if (pending.mType == TRANSIT_TO_BACK) {
+ // TO_BACK is only used when setting the task view visibility immediately,
+ // so in that case we can also hide the surface immediately
+ startTransaction.hide(chg.getLeash());
+ }
tv.prepareHideAnimation(finishTransaction);
} else {
tv.prepareCloseAnimation();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4897c4e..9f20f49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -19,8 +19,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -34,22 +38,25 @@
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
-import com.android.wm.shell.desktopmode.DesktopModeController;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
@@ -57,7 +64,9 @@
import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
+import java.util.Map;
import java.util.Optional;
+import java.util.function.Consumer;
/**
* A handler for dealing with transitions involving multiple other handlers. For example: an
@@ -71,11 +80,11 @@
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private final KeyguardTransitionHandler mKeyguardHandler;
- private DesktopModeController mDesktopModeController;
private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
+ private ActivityEmbeddingController mActivityEmbeddingController;
- private static class MixedTransition {
+ private class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -90,11 +99,17 @@
/** Keyguard exit/occlude/unocclude transition. */
static final int TYPE_KEYGUARD = 5;
- /** Recents Transition while in desktop mode. */
- static final int TYPE_RECENTS_DURING_DESKTOP = 6;
+ /** Recents transition on top of the lock screen. */
+ static final int TYPE_RECENTS_DURING_KEYGUARD = 6;
- /** Fuld/Unfold transition. */
- static final int TYPE_UNFOLD = 7;
+ /** Recents Transition while in desktop mode. */
+ static final int TYPE_RECENTS_DURING_DESKTOP = 7;
+
+ /** Fold/Unfold transition. */
+ static final int TYPE_UNFOLD = 8;
+
+ /** Enter pip from one of the Activity Embedding windows. */
+ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9;
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -110,7 +125,10 @@
final IBinder mTransition;
Transitions.TransitionHandler mLeftoversHandler = null;
+ TransitionInfo mInfo = null;
WindowContainerTransaction mFinishWCT = null;
+ SurfaceControl.Transaction mFinishT = null;
+ Transitions.TransitionFinishCallback mFinishCB = null;
/**
* Whether the transition has request for remote transition while mLeftoversHandler
@@ -131,6 +149,37 @@
mTransition = transition;
}
+ boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ if (mInfo != null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "startSubAnimation #%d.%d", mInfo.getDebugId(), info.getDebugId());
+ }
+ mInFlightSubAnimations++;
+ if (!handler.startAnimation(
+ mTransition, info, startT, finishT, wct -> onSubAnimationFinished(info, wct))) {
+ mInFlightSubAnimations--;
+ return false;
+ }
+ return true;
+ }
+
+ void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
+ mInFlightSubAnimations--;
+ if (mInfo != null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "onSubAnimationFinished #%d.%d remaining=%d",
+ mInfo.getDebugId(), info.getDebugId(), mInFlightSubAnimations);
+ }
+
+ joinFinishArgs(wct);
+
+ if (mInFlightSubAnimations == 0) {
+ mActiveTransitions.remove(MixedTransition.this);
+ mFinishCB.onTransitionFinished(mFinishWCT);
+ }
+ }
+
void joinFinishArgs(WindowContainerTransaction wct) {
if (wct != null) {
if (mFinishWCT == null) {
@@ -149,9 +198,9 @@
@Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
- Optional<DesktopModeController> desktopModeControllerOptional,
Optional<DesktopTasksController> desktopTasksControllerOptional,
- Optional<UnfoldTransitionHandler> unfoldHandler) {
+ Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -169,9 +218,9 @@
if (mRecentsHandler != null) {
mRecentsHandler.addMixer(this);
}
- mDesktopModeController = desktopModeControllerOptional.orElse(null);
mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
+ mActivityEmbeddingController = activityEmbeddingController.orElse(null);
}, this);
}
}
@@ -194,6 +243,16 @@
mPipHandler.augmentRequest(transition, request, out);
mSplitHandler.addEnterOrExitIfNeeded(request, out);
return out;
+ } else if (request.getType() == TRANSIT_PIP
+ && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
+ mActivityEmbeddingController != null)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Got a PiP-enter request from an Activity Embedding split");
+ mActiveTransitions.add(new MixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
+ // Postpone transition splitting to later.
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ return out;
} else if (request.getRemoteTransition() != null
&& TransitionUtil.isOpeningType(request.getType())
&& (request.getTriggerTask() == null
@@ -239,7 +298,7 @@
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
return handler.second;
- } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) {
+ } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) {
final WindowContainerTransaction wct =
mUnfoldHandler.handleRequest(transition, request);
if (wct != null) {
@@ -254,34 +313,46 @@
}
@Override
- public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
- if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
- || DesktopModeStatus.isActive(mPlayer.getContext()))) {
- return this;
+ public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
+ if (mRecentsHandler != null) {
+ if (mSplitHandler.isSplitScreenVisible()) {
+ return this::setRecentsTransitionDuringSplit;
+ } else if (mKeyguardHandler.isKeyguardShowing()) {
+ return this::setRecentsTransitionDuringKeyguard;
+ } else if (mDesktopTasksController != null
+ // Check on the default display. Recents/gesture nav is only available there
+ && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+ return this::setRecentsTransitionDuringDesktop;
+ }
}
return null;
}
- @Override
- public void setRecentsTransition(IBinder transition) {
- if (mSplitHandler.isSplitScreenVisible()) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
- + "Split-Screen is foreground, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
- } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
- + "desktop mode is active, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
- } else {
- throw new IllegalStateException("Accepted a recents transition but don't know how to"
- + " handle it");
- }
+ private void setRecentsTransitionDuringSplit(IBinder transition) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is foreground, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
+ }
+
+ private void setRecentsTransitionDuringKeyguard(IBinder transition) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "keyguard is visible, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
+ }
+
+ private void setRecentsTransitionDuringDesktop(IBinder transition) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "desktop mode is active, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
}
private TransitionInfo subCopy(@NonNull TransitionInfo info,
@@ -352,6 +423,9 @@
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
+ finishTransaction, finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -385,6 +459,9 @@
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
return animateKeyguard(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
+ return animateRecentsDuringKeyguard(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
finishCallback);
@@ -397,6 +474,58 @@
}
}
+ private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP from an Activity Embedding window");
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mixed.mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mixed.mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
+ startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -511,8 +640,26 @@
// make a new startTransaction because pip's startEnterAnimation "consumes" it so
// we need a separate one to send over to launcher.
SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+ @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+ if (mSplitHandler.isSplitScreenVisible()) {
+ // The non-going home case, we could be pip-ing one of the split stages and keep
+ // showing the other
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange) {
+ // Ignore the change/task that's going into Pip
+ continue;
+ }
+ @SplitScreen.StageType int splitItemStage =
+ mSplitHandler.getSplitItemStage(change.getLastParent());
+ if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+ topStageToKeep = splitItemStage;
+ break;
+ }
+ }
+ }
// Let split update internal state for dismiss.
- mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED,
+ mSplitHandler.prepareDismissAnimation(topStageToKeep,
EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
finishTransaction);
@@ -669,24 +816,28 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mixed.mInFlightSubAnimations--;
- if (mixed.mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct);
- }
- };
- mixed.mInFlightSubAnimations++;
+ if (mixed.mFinishT == null) {
+ mixed.mFinishT = finishTransaction;
+ mixed.mFinishCB = finishCallback;
+ }
// Sync pip state.
if (mPipHandler != null) {
mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
}
- if (!mKeyguardHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCB)) {
- mixed.mInFlightSubAnimations--;
- return false;
+ return mixed.startSubAnimation(mKeyguardHandler, info, startTransaction, finishTransaction);
+ }
+
+ private boolean animateRecentsDuringKeyguard(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mixed.mInfo == null) {
+ mixed.mInfo = info;
+ mixed.mFinishT = finishTransaction;
+ mixed.mFinishCB = finishCallback;
}
- return true;
+ return mixed.startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
}
private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
@@ -694,17 +845,21 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ Transitions.TransitionFinishCallback finishCB = wct -> {
+ mixed.mInFlightSubAnimations--;
+ if (mixed.mInFlightSubAnimations == 0) {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct);
+ }
+ };
+
+ mixed.mInFlightSubAnimations++;
boolean consumed = mRecentsHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
if (!consumed) {
+ mixed.mInFlightSubAnimations--;
return false;
}
- //Sync desktop mode state (proto 1)
- if (mDesktopModeController != null) {
- mDesktopModeController.syncSurfaceState(info, finishTransaction);
- return true;
- }
- //Sync desktop mode state (proto 2)
if (mDesktopTasksController != null) {
mDesktopTasksController.syncSurfaceState(info, finishTransaction);
return true;
@@ -747,6 +902,18 @@
return false;
}
+ /** Use to when split use taskId to enter, check if this enter transition should be mixed or
+ * not.*/
+ public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+ // Check if this intent package is same as pip one or not, if true we want let the pip
+ // task enter split.
+ if (mPipHandler != null) {
+ return mPipHandler.isInPipPackage(
+ SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
+ }
+ return false;
+ }
+
/** @return whether the transition-request represents a pip-entry. */
public boolean requestHasPipEnter(TransitionRequestInfo request) {
return mPipHandler.requestHasPipEnter(request);
@@ -786,6 +953,10 @@
} else {
mPipHandler.end();
}
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mPipHandler.end();
if (mixed.mLeftoversHandler != null) {
@@ -802,6 +973,15 @@
finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ handoverTransitionLeashes(mixed, info, t, mixed.mFinishT);
+ if (animateKeyguard(mixed, info, t, mixed.mFinishT, mixed.mFinishCB)) {
+ finishCallback.onTransitionFinished(null);
+ }
+ }
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
@@ -826,6 +1006,9 @@
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -841,4 +1024,38 @@
mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
}
}
+
+ /**
+ * Update an incoming {@link TransitionInfo} with the leashes from an ongoing
+ * {@link MixedTransition} so that it can take over some parts of the animation without
+ * reparenting to new transition roots.
+ */
+ private static void handoverTransitionLeashes(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT) {
+
+ // Show the roots in case they contain new changes not present in the original transition.
+ for (int j = info.getRootCount() - 1; j >= 0; --j) {
+ startT.show(info.getRoot(j).getLeash());
+ }
+
+ // Find all of the leashes from the original transition.
+ Map<WindowContainerToken, TransitionInfo.Change> originalChanges = new ArrayMap<>();
+ for (TransitionInfo.Change oldChange : mixed.mInfo.getChanges()) {
+ if (oldChange.getContainer() != null) {
+ originalChanges.put(oldChange.getContainer(), oldChange);
+ }
+ }
+
+ // Merge the animation leashes by re-using the original ones if we see the same container
+ // in the new transition and the old.
+ for (TransitionInfo.Change newChange : info.getChanges()) {
+ if (originalChanges.containsKey(newChange.getContainer())) {
+ final TransitionInfo.Change oldChange = originalChanges.get(newChange.getContainer());
+ startT.reparent(newChange.getLeash(), null);
+ newChange.setLeash(oldChange.getLeash());
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index de03f58..193a4fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,7 @@
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -39,7 +40,6 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -60,6 +60,7 @@
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.animation.Animator;
@@ -421,15 +422,11 @@
continue;
}
- // The back gesture has animated this change before transition happen, so here we don't
- // play the animation again.
- if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
- continue;
- }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
- Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
+ final int type = getTransitionTypeFromInfo(info);
+ Animation a = loadAnimation(type, info, change, wallpaperTransit, isDreamTransition);
if (a != null) {
if (isTask) {
final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
@@ -515,7 +512,7 @@
}
if (backgroundColorForTransition != 0) {
- addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
+ addBackgroundColor(info, backgroundColorForTransition, startTransaction,
finishTransaction);
}
@@ -546,7 +543,7 @@
return true;
}
- private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
+ private void addBackgroundColor(@NonNull TransitionInfo info,
@ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
final Color bgColor = Color.valueOf(color);
@@ -558,9 +555,19 @@
.setName("animation-background")
.setCallsite("DefaultTransitionHandler")
.setColorLayer();
-
- mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
final SurfaceControl backgroundSurface = colorLayerBuilder.build();
+
+ // Attaching the background surface to the transition root could unexpectedly make it
+ // cover one of the split root tasks. To avoid this, put the background surface just
+ // above the display area when split is on.
+ final boolean isSplitTaskInvolved =
+ info.getChanges().stream().anyMatch(c-> c.getTaskInfo() != null
+ && c.getTaskInfo().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW);
+ if (isSplitTaskInvolved) {
+ mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
+ } else {
+ startTransaction.reparent(backgroundSurface, info.getRootLeash());
+ }
startTransaction.setColor(backgroundSurface, colorArray)
.setLayer(backgroundSurface, -1)
.show(backgroundSurface);
@@ -655,12 +662,11 @@
}
@Nullable
- private Animation loadAnimation(@NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change, int wallpaperTransit,
- boolean isDreamTransition) {
+ private Animation loadAnimation(@WindowManager.TransitionType int type,
+ @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+ int wallpaperTransit, boolean isDreamTransition) {
Animation a;
- final int type = info.getType();
final int flags = info.getFlags();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
@@ -716,7 +722,7 @@
return null;
} else {
a = loadAttributeAnimation(
- info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
+ type, info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
}
if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
new file mode 100644
index 0000000..473deba
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 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.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+
+import static com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving the home
+ * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ */
+public class HomeTransitionObserver implements TransitionObserver,
+ RemoteCallable<HomeTransitionObserver> {
+ private SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+ mListener;
+
+ private @NonNull final Context mContext;
+ private @NonNull final ShellExecutor mMainExecutor;
+ public HomeTransitionObserver(@NonNull Context context,
+ @NonNull ShellExecutor mainExecutor) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null
+ || taskInfo.taskId == -1
+ || !taskInfo.isRunning) {
+ continue;
+ }
+
+ final int mode = change.getMode();
+ final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
+ && (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture)) {
+ notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) || isBackGesture);
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {}
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged,
+ @NonNull IBinder playing) {}
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition,
+ boolean aborted) {}
+
+ /**
+ * Sets the home transition listener that receives any transitions resulting in a change of
+ *
+ */
+ public void setHomeTransitionListener(Transitions transitions,
+ IHomeTransitionListener listener) {
+ if (mListener == null) {
+ mListener = new SingleInstanceRemoteListener<>(this,
+ c -> transitions.registerObserver(this),
+ c -> transitions.unregisterObserver(this));
+ }
+
+ if (listener != null) {
+ mListener.register(listener);
+ } else {
+ mListener.unregister();
+ }
+ }
+
+ /**
+ * Notifies the listener that the home visibility has changed.
+ * @param isVisible true when home activity is visible, false otherwise.
+ */
+ public void notifyHomeVisibilityChanged(boolean isVisible) {
+ if (mListener != null) {
+ mListener.call(l -> l.onHomeVisibilityChanged(isVisible));
+ }
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Invalidates this controller, preventing future calls to send updates.
+ */
+ public void invalidate(Transitions transitions) {
+ transitions.unregisterObserver(this);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
new file mode 100644
index 0000000..18716c6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.transition;
+
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ */
+interface IHomeTransitionListener {
+
+ /**
+ * Called when a transition changes the visibility of the home activity.
+ */
+ void onHomeVisibilityChanged(in boolean isVisible);
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index cc4d268..7f4a8f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -16,9 +16,12 @@
package com.android.wm.shell.transition;
+import android.view.SurfaceControl;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
+import com.android.wm.shell.transition.IHomeTransitionListener;
+
/**
* Interface that is exposed to remote callers to manipulate the transitions feature.
*/
@@ -39,4 +42,14 @@
* Retrieves the apply-token used by transactions in Shell
*/
IBinder getShellApplyToken() = 3;
+
+ /**
+ * Set listener that will receive callbacks about transitions involving home activity.
+ */
+ oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4;
+
+ /**
+ * Returns a container surface for the home root task.
+ */
+ SurfaceControl getHomeTaskOverlayContainer() = 5;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index fab2dd2..4355ed2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -44,7 +44,7 @@
private IBinder mTransition = null;
/** The remote to delegate animation to */
- private final RemoteTransition mRemote;
+ private RemoteTransition mRemote;
public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor,
@NonNull RemoteTransition remote) {
@@ -63,7 +63,7 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mTransition != transition) return false;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for #%d.", mRemote, info.getDebugId());
+ + " transition %s for (#%d).", mRemote, info.getDebugId());
final IBinder.DeathRecipient remoteDied = () -> {
Log.e(Transitions.TAG, "Remote transition died, finishing");
@@ -83,6 +83,7 @@
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
});
+ mRemote = null;
}
};
Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -105,6 +106,7 @@
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
finishCallback.onTransitionFinished(null /* wct */);
+ mRemote = null;
}
return true;
}
@@ -123,6 +125,7 @@
// so just assume the worst-case and clear the local transaction.
t.clear();
mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
+ mRemote = null;
}
};
try {
@@ -152,6 +155,16 @@
}
@Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ try {
+ mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted);
+ } catch (RemoteException e) {
+ Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e);
+ }
+ }
+
+ @Override
public String toString() {
return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
+ mRemote.getRemoteTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index bbf67a6..293b660 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -86,7 +86,16 @@
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {
- mRequestedRemotes.remove(transition);
+ RemoteTransition remoteTransition = mRequestedRemotes.remove(transition);
+ if (remoteTransition == null) {
+ return;
+ }
+
+ try {
+ remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delegating onTransitionConsumed()", e);
+ }
}
@Override
@@ -117,7 +126,7 @@
}
}
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
info.getDebugId(), pendingRemote);
if (pendingRemote == null) return false;
@@ -137,20 +146,24 @@
});
}
};
- Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT =
+ copyIfLocal(startTransaction, remote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
try {
- // If the remote is actually in the same process, then make a copy of parameters since
- // remote impls assume that they have to clean-up native references.
- final SurfaceControl.Transaction remoteStartT =
- copyIfLocal(startTransaction, remote.getRemoteTransition());
- final TransitionInfo remoteInfo =
- remoteStartT == startTransaction ? info : info.localRemoteCopy();
handleDeath(remote.asBinder(), finishCallback);
remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
+ Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
+ if (remoteStartT != startTransaction) {
+ remoteStartT.close();
+ }
+ startTransaction.apply();
unhandleDeath(remote.asBinder(), finishCallback);
mRequestedRemotes.remove(transition);
mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
@@ -232,7 +245,7 @@
if (remote == null) return null;
mRequestedRemotes.put(transition, remote);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
- + " for %s: %s", transition, remote);
+ + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
index e27e4f9..5919aad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
@@ -129,13 +129,12 @@
* Adds an entry in the trace to log that a request to merge a transition was made.
*
* @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
- * @param playingTransitionId The id of the transition we was to merge the transition into.
*/
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergeRequestedTransitionId;
proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
- proto.mergedInto = playingTransitionId;
+ proto.mergeTarget = playingTransitionId;
mTraceBuffer.add(proto);
}
@@ -150,7 +149,7 @@
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergedTransitionId;
proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
- proto.mergedInto = playingTransitionId;
+ proto.mergeTarget = playingTransitionId;
mTraceBuffer.add(proto);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index d978eaf..b012d35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -45,6 +46,7 @@
import android.graphics.Shader;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.ScreenCapture;
@@ -61,10 +63,10 @@
/** Loads the animation that is defined through attribute id for the given transition. */
@Nullable
- public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
+ public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
+ @NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, int wallpaperTransit,
@NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
- final int type = info.getType();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
final boolean enter = TransitionUtil.isOpeningType(changeMode);
@@ -186,6 +188,38 @@
return options.getCustomActivityTransition(isOpen);
}
+ /**
+ * Gets the final transition type from {@link TransitionInfo} for determining the animation.
+ */
+ public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) {
+ final int type = info.getType();
+ // If the info transition type is opening transition, iterate its changes to see if it
+ // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation.
+ if (type == TRANSIT_OPEN) {
+ boolean hasOpenTransit = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
+ && !TransitionUtil.isOrderOnly(change)) {
+ // This isn't an activity-level transition.
+ return type;
+ }
+ if (change.getTaskInfo() != null
+ && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
+ // Ignore non-activity containers.
+ continue;
+ }
+ if (change.getMode() == TRANSIT_OPEN) {
+ hasOpenTransit = true;
+ break;
+ }
+ }
+ if (!hasOpenTransit) {
+ return TRANSIT_CLOSE;
+ }
+ }
+ return type;
+ }
+
static Animation loadCustomActivityTransition(
@NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
TransitionInfo.AnimationOptions options, boolean enter,
@@ -346,7 +380,7 @@
.setFrameScale(1)
.setPixelFormat(PixelFormat.RGBA_8888)
.setChildrenOnly(true)
- .setAllowProtected(true)
+ .setAllowProtected(false)
.setCaptureSecureLayers(true)
.build();
final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index c74b3f3..af69b52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -28,6 +28,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -63,7 +64,6 @@
import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
import androidx.annotation.BinderThread;
@@ -71,6 +71,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -149,19 +150,19 @@
/** Transition type for maximize to freeform transition. */
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
- /** Transition type for starting the move to desktop mode. */
- public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE =
+ /** Transition type for starting the drag to desktop mode. */
+ public static final int TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP =
WindowManager.TRANSIT_FIRST_CUSTOM + 10;
- /** Transition type for finalizing the move to desktop mode. */
- public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE =
+ /** Transition type for finalizing the drag to desktop mode. */
+ public static final int TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP =
WindowManager.TRANSIT_FIRST_CUSTOM + 11;
/** Transition type to fullscreen from desktop mode. */
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
- /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
- public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE =
+ /** Transition type to cancel the drag to desktop mode. */
+ public static final int TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP =
WindowManager.TRANSIT_FIRST_CUSTOM + 13;
/** Transition type to animate the toggle resize between the max and default desktop sizes. */
@@ -171,7 +172,7 @@
/** Transition to animate task to desktop. */
public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
- private final WindowOrganizer mOrganizer;
+ private final ShellTaskOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
@@ -179,6 +180,7 @@
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
@@ -188,11 +190,10 @@
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
- @Nullable
- private final ShellCommandHandler mShellCommandHandler;
-
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
+ private HomeTransitionObserver mHomeTransitionObserver;
+
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -206,12 +207,6 @@
*/
private static final int SYNC_ALLOWANCE_MS = 120;
- /**
- * Keyguard gets a more generous timeout to finish its animations, because we are always holding
- * a sleep token during occlude/unocclude transitions and we want them to finish playing cleanly
- */
- private static final int SYNC_ALLOWANCE_KEYGUARD_MS = 2000;
-
/** For testing only. Disables the force-finish timeout on sync. */
private boolean mDisableForceSync = false;
@@ -237,7 +232,7 @@
@Override
public String toString() {
if (mInfo != null && mInfo.getDebugId() >= 0) {
- return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
+ return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack();
}
return mToken.toString() + "@" + getTrack();
}
@@ -269,28 +264,30 @@
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
- @NonNull WindowOrganizer organizer,
- @NonNull TransactionPool pool,
- @NonNull DisplayController displayController,
- @NonNull ShellExecutor mainExecutor,
- @NonNull Handler mainHandler,
- @NonNull ShellExecutor animExecutor) {
- this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor, null,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context));
- }
-
- public Transitions(@NonNull Context context,
- @NonNull ShellInit shellInit,
- @NonNull ShellController shellController,
- @NonNull WindowOrganizer organizer,
+ @NonNull ShellTaskOrganizer organizer,
@NonNull TransactionPool pool,
@NonNull DisplayController displayController,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
+ @NonNull HomeTransitionObserver observer) {
+ this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor,
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit), observer);
+ }
+
+ public Transitions(@NonNull Context context,
+ @NonNull ShellInit shellInit,
@Nullable ShellCommandHandler shellCommandHandler,
- @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
+ @NonNull ShellController shellController,
+ @NonNull ShellTaskOrganizer organizer,
+ @NonNull TransactionPool pool,
+ @NonNull DisplayController displayController,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor,
+ @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ @NonNull HomeTransitionObserver observer) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -300,15 +297,16 @@
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
- mShellCommandHandler = shellCommandHandler;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
+ mHomeTransitionObserver = observer;
}
private void onInit() {
@@ -339,9 +337,8 @@
TransitionMetrics.getInstance();
}
- if (mShellCommandHandler != null) {
- mShellCommandHandler.addCommandCallback("transitions", this, this);
- }
+ mShellCommandHandler.addCommandCallback("transitions", this, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
public boolean isRegistered() {
@@ -655,14 +652,29 @@
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
- transitionToken, info);
- final int activeIdx = findByToken(mPendingTransitions, transitionToken);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+ info.getDebugId(), transitionToken, info);
+ int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
- throw new IllegalStateException("Got transitionReady for non-pending transition "
+ final ActiveTransition existing = getKnownTransition(transitionToken);
+ if (existing != null) {
+ Log.e(TAG, "Got duplicate transitionReady for " + transitionToken);
+ // The transition is already somewhere else in the pipeline, so just return here.
+ t.apply();
+ existing.mFinishT.merge(finishT);
+ return;
+ }
+ // This usually means the system is in a bad state and may not recover; however,
+ // there's an incentive to propagate bad states rather than crash, so we're kinda
+ // required to do the same thing I guess.
+ Log.wtf(TAG, "Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
+ Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
+ final ActiveTransition fallback = new ActiveTransition();
+ fallback.mToken = transitionToken;
+ mPendingTransitions.add(fallback);
+ activeIdx = mPendingTransitions.size() - 1;
}
// Move from pending to ready
final ActiveTransition active = mPendingTransitions.remove(activeIdx);
@@ -746,6 +758,11 @@
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
}
+ // The change has already animated by back gesture, don't need to play transition
+ // animation on it.
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ info.getChanges().remove(i);
+ }
}
// There does not need animation when:
// A. Transfer starting window. Apply transfer starting window directly if there is no other
@@ -1048,34 +1065,43 @@
processReadyQueue(track);
}
- private boolean isTransitionKnown(IBinder token) {
+ /**
+ * Checks to see if the transition specified by `token` is already known. If so, it will be
+ * returned.
+ */
+ @Nullable
+ private ActiveTransition getKnownTransition(IBinder token) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
- if (mPendingTransitions.get(i).mToken == token) return true;
+ final ActiveTransition active = mPendingTransitions.get(i);
+ if (active.mToken == token) return active;
}
for (int i = 0; i < mReadyDuringSync.size(); ++i) {
- if (mReadyDuringSync.get(i).mToken == token) return true;
+ final ActiveTransition active = mReadyDuringSync.get(i);
+ if (active.mToken == token) return active;
}
for (int t = 0; t < mTracks.size(); ++t) {
final Track tr = mTracks.get(t);
for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
- if (tr.mReadyTransitions.get(i).mToken == token) return true;
+ final ActiveTransition active = tr.mReadyTransitions.get(i);
+ if (active.mToken == token) return active;
}
final ActiveTransition active = tr.mActiveTransition;
if (active == null) continue;
- if (active.mToken == token) return true;
+ if (active.mToken == token) return active;
if (active.mMerged == null) continue;
for (int m = 0; m < active.mMerged.size(); ++m) {
- if (active.mMerged.get(m).mToken == token) return true;
+ final ActiveTransition merged = active.mMerged.get(m);
+ if (merged.mToken == token) return merged;
}
}
- return false;
+ return null;
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
- transitionToken, request);
- if (isTransitionKnown(transitionToken)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+ request.getDebugId(), transitionToken, request);
+ if (getKnownTransition(transitionToken) != null) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
@@ -1159,7 +1185,7 @@
*/
private void finishForSync(ActiveTransition reason,
int trackIdx, @Nullable ActiveTransition forceFinish) {
- if (!isTransitionKnown(reason.mToken)) {
+ if (getKnownTransition(reason.mToken) == null) {
Log.d(TAG, "finishForSleep: already played sync transition " + reason);
return;
}
@@ -1206,17 +1232,18 @@
if (track.mActiveTransition == playing) {
if (!mDisableForceSync) {
// Give it a short amount of time to process it before forcing.
- final int tolerance = KeyguardTransitionHandler.handles(playing.mInfo)
- ? SYNC_ALLOWANCE_KEYGUARD_MS
- : SYNC_ALLOWANCE_MS;
mMainExecutor.executeDelayed(
- () -> finishForSync(reason, trackIdx, playing), tolerance);
+ () -> finishForSync(reason, trackIdx, playing), SYNC_ALLOWANCE_MS);
}
break;
}
}
}
+ private SurfaceControl getHomeTaskOverlayContainer() {
+ return mOrganizer.getHomeTaskOverlayContainer();
+ }
+
/**
* Interface for a callback that must be called after a TransitionHandler finishes playing an
* animation.
@@ -1413,6 +1440,7 @@
*/
@Override
public void invalidate() {
+ mTransitions.mHomeTransitionObserver.invalidate(mTransitions);
mTransitions = null;
}
@@ -1437,6 +1465,26 @@
public IBinder getShellApplyToken() {
return SurfaceControl.Transaction.getDefaultApplyToken();
}
+
+ @Override
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
+ (transitions) -> {
+ transitions.mHomeTransitionObserver.setHomeTransitionListener(mTransitions,
+ listener);
+ });
+ }
+
+ @Override
+ public SurfaceControl getHomeTaskOverlayContainer() {
+ SurfaceControl[] result = new SurfaceControl[1];
+ executeRemoteCallWithTaskPermission(mTransitions, "getHomeTaskOverlayContainer",
+ (controller) -> {
+ result[0] = controller.getHomeTaskOverlayContainer();
+ }, true /* blocking */);
+ // Return a copy as writing to parcel releases the original surface
+ return new SurfaceControl(result[0], "Transitions.HomeOverlay");
+ }
}
private class SettingsObserver extends ContentObserver {
@@ -1475,4 +1523,68 @@
pw.println(prefix + "tracing");
mTracer.printShellCommandHelp(pw, prefix + " ");
}
+
+ private void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "Handlers:");
+ for (TransitionHandler handler : mHandlers) {
+ pw.print(innerPrefix);
+ pw.print(handler.getClass().getSimpleName());
+ pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
+ }
+
+ pw.println(prefix + "Observers:");
+ for (TransitionObserver observer : mObservers) {
+ pw.print(innerPrefix);
+ pw.println(observer.getClass().getSimpleName());
+ }
+
+ pw.println(prefix + "Pending Transitions:");
+ for (ActiveTransition transition : mPendingTransitions) {
+ pw.print(innerPrefix + "token=");
+ pw.println(transition.mToken);
+ pw.print(innerPrefix + "id=");
+ pw.println(transition.mInfo != null
+ ? transition.mInfo.getDebugId()
+ : -1);
+ pw.print(innerPrefix + "handler=");
+ pw.println(transition.mHandler != null
+ ? transition.mHandler.getClass().getSimpleName()
+ : null);
+ }
+ if (mPendingTransitions.isEmpty()) {
+ pw.println(innerPrefix + "none");
+ }
+
+ pw.println(prefix + "Ready-during-sync Transitions:");
+ for (ActiveTransition transition : mReadyDuringSync) {
+ pw.print(innerPrefix + "token=");
+ pw.println(transition.mToken);
+ pw.print(innerPrefix + "id=");
+ pw.println(transition.mInfo != null
+ ? transition.mInfo.getDebugId()
+ : -1);
+ pw.print(innerPrefix + "handler=");
+ pw.println(transition.mHandler != null
+ ? transition.mHandler.getClass().getSimpleName()
+ : null);
+ }
+ if (mReadyDuringSync.isEmpty()) {
+ pw.println(innerPrefix + "none");
+ }
+
+ pw.println(prefix + "Tracks:");
+ for (int i = 0; i < mTracks.size(); i++) {
+ final ActiveTransition transition = mTracks.get(i).mActiveTransition;
+ pw.println(innerPrefix + "Track #" + i);
+ pw.print(innerPrefix + "active=");
+ pw.println(transition);
+ if (transition != null) {
+ pw.print(innerPrefix + "hander=");
+ pw.println(transition.mHandler);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 68b5a81..98d343b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -16,10 +16,13 @@
package com.android.wm.shell.unfold;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
+import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -74,9 +77,9 @@
Executor executor,
Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransitions = transitions;
mTransactionPool = transactionPool;
mExecutor = executor;
- mTransitions = transitions;
mAnimators.add(splitUnfoldTaskAnimator);
mAnimators.add(fullscreenUnfoldAnimator);
@@ -104,6 +107,16 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
+ if (shouldPlayUnfoldAnimation(info) && transition != mTransition) {
+ // Take over transition that has unfold, we might receive it if no other handler
+ // accepted request in handleRequest, e.g. for rotation + unfold or
+ // TRANSIT_NONE + unfold transitions
+ mTransition = transition;
+
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "UnfoldTransitionHandler: "
+ + "take over startAnimation");
+ }
+
if (transition != mTransition) return false;
for (int i = 0; i < mAnimators.size(); i++) {
@@ -177,37 +190,96 @@
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull TransitionFinishCallback finishCallback) {
- if (info.getType() == TRANSIT_CHANGE) {
- // TODO (b/286928742) unfold transition handler should be part of mixed handler to
- // handle merges better.
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo != null
- && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
- // Tasks that are always on top (e.g. bubbles), will handle their own transition
- // as they are on top of everything else. So skip merging transitions here.
- return;
- }
- }
- // Apply changes happening during the unfold animation immediately
- t.apply();
- finishCallback.onTransitionFinished(null);
+ if (info.getType() != TRANSIT_CHANGE) {
+ return;
}
+ if ((info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
+ return;
+ }
+ // TODO (b/286928742) unfold transition handler should be part of mixed handler to
+ // handle merges better.
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+ // Tasks that are always on top (e.g. bubbles), will handle their own transition
+ // as they are on top of everything else. So skip merging transitions here.
+ return;
+ }
+ }
+ // Apply changes happening during the unfold animation immediately
+ t.apply();
+ finishCallback.onTransitionFinished(null);
}
/** Whether `request` contains an unfold action. */
- public boolean hasUnfold(@NonNull TransitionRequestInfo request) {
+ public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) {
+ // Unfold animation won't play when animations are disabled
+ if (!ValueAnimator.areAnimatorsEnabled()) return false;
+
return (request.getType() == TRANSIT_CHANGE
&& request.getDisplayChange() != null
- && request.getDisplayChange().isPhysicalDisplayChanged());
+ && isUnfoldDisplayChange(request.getDisplayChange()));
+ }
+
+ private boolean isUnfoldDisplayChange(
+ @NonNull TransitionRequestInfo.DisplayChange displayChange) {
+ if (!displayChange.isPhysicalDisplayChanged()) {
+ return false;
+ }
+
+ if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) {
+ return false;
+ }
+
+ // Handle only unfolding, currently we don't have an animation when folding
+ final int endArea =
+ displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height();
+ final int startArea = displayChange.getStartAbsBounds().width()
+ * displayChange.getStartAbsBounds().height();
+
+ return endArea > startArea;
+ }
+
+ /** Whether `transitionInfo` contains an unfold action. */
+ public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) {
+ // Unfold animation won't play when animations are disabled
+ if (!ValueAnimator.areAnimatorsEnabled()) return false;
+ // Only handle transitions that are marked as physical display switch
+ // See PhysicalDisplaySwitchTransitionLauncher for the conditions
+ if ((transitionInfo.getFlags() & TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH) == 0) return false;
+
+ for (int i = 0; i < transitionInfo.getChanges().size(); i++) {
+ final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
+ // We are interested only in display container changes
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) == 0) {
+ continue;
+ }
+
+ // Handle only unfolding, currently we don't have an animation when folding
+ if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) {
+ continue;
+ }
+
+ final int afterArea =
+ change.getEndAbsBounds().width() * change.getEndAbsBounds().height();
+ final int beforeArea = change.getStartAbsBounds().width()
+ * change.getStartAbsBounds().height();
+
+ if (afterArea > beforeArea) {
+ return true;
+ }
+ }
+
+ return false;
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (hasUnfold(request)) {
+ if (shouldPlayUnfoldAnimation(request)) {
mTransition = transition;
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index f209521..3e06d2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
+
import java.util.Objects;
/**
@@ -26,6 +28,8 @@
* tasks/leashes/etc in Launcher
*/
public class SplitBounds implements Parcelable {
+ public static final String KEY_EXTRA_SPLIT_BOUNDS = "key_SplitBounds";
+
public final Rect leftTopBounds;
public final Rect rightBottomBounds;
/** This rect represents the actual gap between the two apps */
@@ -35,6 +39,7 @@
public final float leftTaskPercent;
public final float dividerWidthPercent;
public final float dividerHeightPercent;
+ public final @PersistentSnapPosition int snapPosition;
/**
* If {@code true}, that means at the time of creation of this object, the
* split-screened apps were vertically stacked. This is useful in scenarios like
@@ -45,12 +50,13 @@
public final int leftTopTaskId;
public final int rightBottomTaskId;
- public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
- int leftTopTaskId, int rightBottomTaskId) {
+ public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
+ int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
this.leftTopTaskId = leftTopTaskId;
this.rightBottomTaskId = rightBottomTaskId;
+ this.snapPosition = snapPosition;
if (rightBottomBounds.top > leftTopBounds.top) {
// vertical apps, horizontal divider
@@ -81,8 +87,9 @@
appsStackedVertically = parcel.readBoolean();
leftTopTaskId = parcel.readInt();
rightBottomTaskId = parcel.readInt();
- dividerWidthPercent = parcel.readInt();
- dividerHeightPercent = parcel.readInt();
+ dividerWidthPercent = parcel.readFloat();
+ dividerHeightPercent = parcel.readFloat();
+ snapPosition = parcel.readInt();
}
@Override
@@ -97,6 +104,7 @@
parcel.writeInt(rightBottomTaskId);
parcel.writeFloat(dividerWidthPercent);
parcel.writeFloat(dividerHeightPercent);
+ parcel.writeInt(snapPosition);
}
@Override
@@ -127,7 +135,8 @@
return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
+ "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n"
+ "Divider: " + visualDividerBounds + "\n"
- + "AppsVertical? " + appsStackedVertically;
+ + "AppsVertical? " + appsStackedVertically + "\n"
+ + "snapPosition: " + snapPosition;
}
public static final Creator<SplitBounds> CREATOR = new Creator<SplitBounds>() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index ce81910..c12ac8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
@@ -64,7 +65,8 @@
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface,
+ taskInfo.getConfiguration());
mHandler = handler;
mChoreographer = choreographer;
@@ -113,7 +115,7 @@
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
+ mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
@@ -147,7 +149,8 @@
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
- mSurfaceControlTransactionSupplier);
+ mSurfaceControlTransactionSupplier,
+ mDisplayController);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -226,7 +229,12 @@
}
@Override
- int getCaptionHeightId() {
+ int getCaptionHeightId(@WindowingMode int windowingMode) {
return R.dimen.freeform_decor_caption_height;
}
+
+ @Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3a0bdd6..ab29df1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,6 +21,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -50,10 +52,13 @@
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -64,22 +69,30 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Supplier;
@@ -93,6 +106,7 @@
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
private final ActivityTaskManager mActivityTaskManager;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellController mShellController;
private final Context mContext;
@@ -100,13 +114,14 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private final Optional<DesktopModeController> mDesktopModeController;
private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final RecentsTransitionHandler mRecentsTransitionHandler;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
- private final TaskCornersListener mCornersListener = new TaskCornersListenerImpl();
+ private final ExclusionRegionListener mExclusionRegionListener =
+ new ExclusionRegionListenerImpl();
private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
new SparseArray<>();
@@ -120,37 +135,46 @@
private MoveToDesktopAnimator mMoveToDesktopAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
- private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener;
+ private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
+ new DesktopModeKeyguardChangeListener();
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private boolean mInImmersiveMode;
public DesktopModeWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
- Optional<DesktopTasksController> desktopTasksController
+ Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
context,
mainHandler,
mainChoreographer,
shellInit,
+ shellCommandHandler,
taskOrganizer,
displayController,
shellController,
+ displayInsetsController,
syncQueue,
transitions,
- desktopModeController,
desktopTasksController,
+ recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
- new DesktopModeKeyguardChangeListener());
+ rootTaskDisplayAreaOrganizer);
}
@VisibleForTesting
@@ -159,17 +183,19 @@
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- DesktopModeKeyguardChangeListener desktopModeKeyguardChangeListener) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -177,21 +203,31 @@
mTaskOrganizer = taskOrganizer;
mShellController = shellController;
mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
mSyncQueue = syncQueue;
mTransitions = transitions;
- mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
-
+ mRecentsTransitionHandler = recentsTransitionHandler;
+ mShellCommandHandler = shellCommandHandler;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
- mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
+ mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
+ @Override
+ public void onTransitionStarted(IBinder transition) {
+ blockRelayoutOnTransitionStarted(transition);
+ }
+ });
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+ new DesktopModeOnInsetsChangedListener());
}
@Override
@@ -204,12 +240,11 @@
mSplitScreenController = splitScreenController;
mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
@Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (visible) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.isActive(mContext)
+ if (decor != null && DesktopModeStatus.isEnabled()
&& decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
}
}
@@ -234,13 +269,23 @@
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+ && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
|| info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition);
+ } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK
+ && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+ && change.getTaskInfo() != null) {
+ final DesktopModeWindowDecoration decor =
+ mWindowDecorByTaskId.get(change.getTaskInfo().taskId);
+ if (decor != null) {
+ decor.addTransitionPausingRelayout(transition);
+ }
+ } else if (change.getMode() == WindowManager.TRANSIT_TO_FRONT
+ && ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
+ && change.getTaskInfo() != null) {
+ blockRelayoutOnTransitionStarted(transition);
}
}
@@ -318,8 +363,19 @@
}
}
+ private void blockRelayoutOnTransitionStarted(IBinder transition) {
+ // Block relayout on window decorations originating from #onTaskInfoChanges until the
+ // animation completes to avoid interfering with the transition animation.
+ for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ decor.incrementRelayoutBlock();
+ decor.addTransitionPausingRelayout(transition);
+ }
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
- implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+ implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
+ DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -328,6 +384,7 @@
private final GestureDetector mGestureDetector;
private boolean mIsDragging;
+ private boolean mHasLongClicked;
private boolean mShouldClick;
private int mDragPointerId = -1;
@@ -345,16 +402,11 @@
public void onClick(View v) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
- if (id == R.id.close_window || id == R.id.close_button) {
- mTaskOperations.closeTask(mTaskToken);
- if (mSplitScreenController != null
- && mSplitScreenController.isSplitScreenVisible()) {
- int remainingTaskPosition = mTaskId == mSplitScreenController
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
- .getTaskInfo(remainingTaskPosition);
- mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
+ if (id == R.id.close_window) {
+ if (isTaskInSplitScreen(mTaskId)) {
+ mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId);
+ } else {
+ mTaskOperations.closeTask(mTaskToken);
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
@@ -366,7 +418,6 @@
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
if (mDesktopTasksController.isPresent()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// App sometimes draws before the insets from WindowDecoration#relayout have
@@ -374,12 +425,17 @@
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
decoration.incrementRelayoutBlock();
mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+ closeOtherSplitTask(mTaskId);
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
- mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
+ if (isTaskInSplitScreen(mTaskId)) {
+ mSplitScreenController.moveTaskToFullscreen(mTaskId);
+ } else {
+ mDesktopTasksController.ifPresent(c ->
+ c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
+ }
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.ifPresent(c -> {
@@ -392,13 +448,34 @@
// TODO(b/278084491): dev option to enable display switching
// remove when select is implemented
mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
- decoration.closeHandleMenu();
}
} else if (id == R.id.maximize_window) {
+ if (decoration.isMaximizeMenuActive()) {
+ decoration.closeMaximizeMenu();
+ return;
+ }
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
taskInfo, decoration));
decoration.closeHandleMenu();
+ } else if (id == R.id.maximize_menu_maximize_button) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
+ taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)));
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ } else if (id == R.id.maximize_menu_snap_left_button) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
+ taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT));
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ } else if (id == R.id.maximize_menu_snap_right_button) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
+ taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT));
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
}
}
@@ -406,17 +483,48 @@
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
- && id != R.id.open_menu_button && id != R.id.close_window) {
+ && id != R.id.open_menu_button && id != R.id.close_window
+ && id != R.id.maximize_window) {
return false;
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+
+ if (!mHasLongClicked) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ decoration.closeMaximizeMenu();
+ }
+
+ final long eventDuration = e.getEventTime() - e.getDownTime();
+ final boolean shouldLongClick = id == R.id.maximize_window && !mIsDragging
+ && !mHasLongClicked && eventDuration >= ViewConfiguration.getLongPressTimeout();
+ if (shouldLongClick) {
+ v.performLongClick();
+ mHasLongClicked = true;
+ return true;
+ }
+
return mDragDetector.onMotionEvent(v, e);
}
+ @Override
+ public boolean onLongClick(View v) {
+ final int id = v.getId();
+ if (id == R.id.maximize_window) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ moveTaskToFront(decoration.mTaskInfo);
+ if (decoration.isMaximizeMenuActive()) {
+ decoration.closeMaximizeMenu();
+ } else {
+ decoration.createMaximizeMenu();
+ }
+ return true;
+ }
+ return false;
+ }
+
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
- mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo));
}
}
@@ -427,18 +535,18 @@
@Override
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (DesktopModeStatus.isProto2Enabled()
+ if (DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
}
- if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
- == WINDOWING_MODE_FULLSCREEN) {
- return false;
- }
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
+ if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ // If a motion event is cancelled, reset mShouldClick so a click is not accidentally
+ // performed.
+ mShouldClick = false;
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
@@ -447,11 +555,13 @@
e.getRawY(0));
mIsDragging = false;
mShouldClick = true;
+ mHasLongClicked = false;
return true;
}
case MotionEvent.ACTION_MOVE: {
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
+ decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
@@ -470,7 +580,7 @@
case MotionEvent.ACTION_CANCEL: {
final boolean wasDragging = mIsDragging;
if (!wasDragging) {
- if (mShouldClick && v != null) {
+ if (mShouldClick && v != null && !mHasLongClicked) {
v.performClick();
mShouldClick = false;
return true;
@@ -540,6 +650,15 @@
super.dispose();
}
+ @Override
+ public String toString() {
+ return "EventReceiver"
+ + "{"
+ + "tasksOnDisplay="
+ + mTasksOnDisplay
+ + "}";
+ }
+
private void incrementTaskNumber() {
mTasksOnDisplay++;
}
@@ -585,37 +704,35 @@
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
- if (DesktopModeStatus.isProto2Enabled()) {
- if (relevantDecor == null
+ if (DesktopModeStatus.isEnabled()) {
+ if (!mInImmersiveMode && (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
- || mTransitionDragActive) {
+ || mTransitionDragActive)) {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
handleEventOutsideFocusedCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
if (mTransitionDragActive) {
inputMonitor.pilferPointers();
}
- } else if (DesktopModeStatus.isProto1Enabled()) {
- if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
- inputMonitor.pilferPointers();
- }
}
}
// If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
private void handleEventOutsideFocusedCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
+ // Returns if event occurs within caption
+ if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
+ return;
+ }
+
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (relevantDecor == null) {
- return;
- }
-
if (!mTransitionDragActive) {
relevantDecor.closeHandleMenuIfNeeded(ev);
+ relevantDecor.closeMaximizeMenuIfNeeded(ev);
}
}
}
@@ -634,7 +751,7 @@
mDragToDesktopAnimationStartBounds.set(
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
// In proto2 any full screen or multi-window task can be dragged to
// freeform.
final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
@@ -659,18 +776,14 @@
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > 2 * statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
mMoveToDesktopAnimator = null;
return;
} else if (mMoveToDesktopAnimator != null) {
- relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator));
+ c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo));
mMoveToDesktopAnimator = null;
return;
}
@@ -693,13 +806,23 @@
if (ev.getY() > statusBarHeight) {
if (mMoveToDesktopAnimator == null) {
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
- mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface);
+ mContext, mDragToDesktopAnimationStartBounds,
+ relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
- mDragToDesktopAnimationStartBounds,
- mMoveToDesktopAnimator));
- mMoveToDesktopAnimator.startAnimation();
+ c -> {
+ final int taskId = relevantDecor.mTaskInfo.taskId;
+ relevantDecor.incrementRelayoutBlock();
+ if (isTaskInSplitScreen(taskId)) {
+ final DesktopModeWindowDecoration otherDecor =
+ mWindowDecorByTaskId.get(
+ getOtherSplitTask(taskId).taskId);
+ if (otherDecor != null) {
+ otherDecor.incrementRelayoutBlock();
+ }
+ }
+ c.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator, relevantDecor);
+ });
}
}
if (mMoveToDesktopAnimator != null) {
@@ -738,7 +861,6 @@
*/
private void animateToDesktop(DesktopModeWindowDecoration relevantDecor,
MotionEvent ev) {
- relevantDecor.incrementRelayoutBlock();
centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
}
@@ -754,15 +876,15 @@
final SurfaceControl sc = relevantDecor.mTaskSurface;
final Rect endBounds = calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE);
final Transaction t = mTransactionFactory.get();
- final float diffX = endBounds.centerX() - ev.getX();
- final float diffY = endBounds.top - ev.getY();
- final float startingX = ev.getX() - DRAG_FREEFORM_SCALE
+ final float diffX = endBounds.centerX() - ev.getRawX();
+ final float diffY = endBounds.top - ev.getRawY();
+ final float startingX = ev.getRawX() - DRAG_FREEFORM_SCALE
* mDragToDesktopAnimationStartBounds.width() / 2;
animator.addUpdateListener(animation -> {
final float animatorValue = (float) animation.getAnimatedValue();
final float x = startingX + diffX * animatorValue;
- final float y = ev.getY() + diffY * animatorValue;
+ final float y = ev.getRawY() + diffY * animatorValue;
t.setPosition(sc, x, y);
t.apply();
});
@@ -770,9 +892,11 @@
@Override
public void onAnimationEnd(Animator animation) {
mDesktopTasksController.ifPresent(
- c -> c.onDragPositioningEndThroughStatusBar(
- relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(), FINAL_FREEFORM_SCALE)));
+ c -> {
+ c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(),
+ FINAL_FREEFORM_SCALE));
+ });
}
});
animator.start();
@@ -782,7 +906,8 @@
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
// We can't look at focused task here as only one task will have focus.
- return getSplitScreenDecor(ev);
+ DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
+ return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
} else {
return getFocusedDecor();
}
@@ -853,7 +978,7 @@
&& taskInfo.isFocused) {
return false;
}
- return DesktopModeStatus.isProto2Enabled()
+ return DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
@@ -880,7 +1005,8 @@
taskSurface,
mMainHandler,
mMainChoreographer,
- mSyncQueue);
+ mSyncQueue,
+ mRootTaskDisplayAreaOrganizer);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
windowDecoration.createResizeVeil();
@@ -889,8 +1015,9 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
- windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setCornersListener(mCornersListener);
+ windowDecoration.setCaptionListeners(
+ touchEventListener, touchEventListener, touchEventListener);
+ windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
@@ -912,11 +1039,39 @@
}
}
+ private RunningTaskInfo getOtherSplitTask(int taskId) {
+ @SplitPosition int remainingTaskPosition = mSplitScreenController
+ .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ return mSplitScreenController.getTaskInfo(remainingTaskPosition);
+ }
+
+ private void closeOtherSplitTask(int taskId) {
+ if (isTaskInSplitScreen(taskId)) {
+ mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
+ }
+ }
+
+ private boolean isTaskInSplitScreen(int taskId) {
+ return mSplitScreenController != null
+ && mSplitScreenController.isTaskInSplitScreen(taskId);
+ }
+
+ private void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "DesktopModeWindowDecorViewModel");
+ pw.println(innerPrefix + "DesktopModeStatus=" + DesktopModeStatus.isEnabled());
+ pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
+ pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
+ pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
+ }
+
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
public void onDragStart(int taskId) {
- mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ decoration.closeHandleMenu();
}
}
@@ -926,19 +1081,17 @@
}
}
- private class TaskCornersListenerImpl
- implements DesktopModeWindowDecoration.TaskCornersListener {
+ private class ExclusionRegionListenerImpl
+ implements ExclusionRegionListener {
@Override
- public void onTaskCornersChanged(int taskId, Region corner) {
- mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
- mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
+ public void onExclusionRegionChanged(int taskId, Region region) {
+ mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
}
@Override
- public void onTaskCornersRemoved(int taskId) {
- mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId));
- mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
+ public void onExclusionRegionDismissed(int taskId) {
+ mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
}
}
@@ -957,6 +1110,35 @@
return mIsKeyguardVisible && mIsKeyguardOccluded;
}
}
+
+ @VisibleForTesting
+ class DesktopModeOnInsetsChangedListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ for (int i = 0; i < insetsState.sourceSize(); i++) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() != statusBars()) {
+ continue;
+ }
+
+ final DesktopModeWindowDecoration decor = getFocusedDecor();
+ if (decor == null) {
+ return;
+ }
+ // If status bar inset is visible, top task is not in immersive mode
+ final boolean inImmersiveMode = !source.isVisible();
+ // Calls WindowDecoration#relayout if decoration visibility needs to be updated
+ if (inImmersiveMode != mInImmersiveMode) {
+ decor.relayout(decor.mTaskInfo);
+ mInImmersiveMode = inImmersiveMode;
+ }
+
+ return;
+ }
+ }
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index a359395..6ec91e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -17,12 +17,19 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.windowingModeToString;
+
+import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
import android.app.ActivityManager;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -36,13 +43,17 @@
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.ImageButton;
import android.window.WindowContainerTransaction;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -52,6 +63,7 @@
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Supplier;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -69,6 +81,7 @@
private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
+ private View.OnLongClickListener mOnCaptionLongClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -80,15 +93,19 @@
private final Point mPositionInParent = new Point();
private HandleMenu mHandleMenu;
+ private MaximizeMenu mMaximizeMenu;
+
private ResizeVeil mResizeVeil;
- private Drawable mAppIcon;
+ private Drawable mAppIconDrawable;
+ private Bitmap mAppIconBitmap;
private CharSequence mAppName;
- private TaskCornersListener mCornersListener;
+ private ExclusionRegionListener mExclusionRegionListener;
private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
private int mRelayoutBlock;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
DesktopModeWindowDecoration(
Context context,
@@ -96,38 +113,58 @@
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
+ Configuration windowDecorConfig,
Handler handler,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
+ SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
+ }
+
+ DesktopModeWindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Configuration windowDecorConfig,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ windowContainerTransactionSupplier, surfaceControlSupplier,
+ surfaceControlViewHostFactory);
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
loadAppInfo();
}
- @Override
- protected Configuration getConfigurationWithOverrides(
- ActivityManager.RunningTaskInfo taskInfo) {
- Configuration configuration = taskInfo.getConfiguration();
- if (DesktopTasksController.isDesktopDensityOverrideSet()) {
- // Density is overridden for desktop tasks. Keep system density for window decoration.
- configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
- }
- return configuration;
- }
-
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
- View.OnTouchListener onCaptionTouchListener) {
+ View.OnTouchListener onCaptionTouchListener,
+ View.OnLongClickListener onLongClickListener) {
mOnCaptionButtonClickListener = onCaptionButtonClickListener;
mOnCaptionTouchListener = onCaptionTouchListener;
+ mOnCaptionLongClickListener = onLongClickListener;
}
- void setCornersListener(TaskCornersListener cornersListener) {
- mCornersListener = cornersListener;
+ void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
+ mExclusionRegionListener = exclusionRegionListener;
}
void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
@@ -148,7 +185,7 @@
return;
}
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
@@ -178,9 +215,23 @@
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
+ mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ // The configuration used to lay out the window decoration. The system context's config is
+ // used when the task density has been overridden to a custom density so that the resources
+ // and views of the decoration aren't affected and match the rest of the System UI, if not
+ // then just use the task's configuration. A copy is made instead of using the original
+ // reference so that the configuration isn't mutated on config changes and diff checks can
+ // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
+ // See b/301119301.
+ // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
+ // instead of using a whole Configuration as a parameter.
+ final Configuration windowDecorConfig = new Configuration();
+ windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
+ ? mContext.getResources().getConfiguration() // Use system context.
+ : mTaskInfo.configuration); // Use task configuration.
+ mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
mRelayoutParams.mCornerRadius =
(int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
@@ -207,8 +258,9 @@
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
+ mOnCaptionLongClickListener,
mAppName,
- mAppIcon
+ mAppIconBitmap
);
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
@@ -218,9 +270,14 @@
if (!mTaskInfo.isFocused) {
closeHandleMenu();
+ closeMaximizeMenu();
}
if (!isDragResizeable) {
+ if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
+ // We still want to track caption bar's exclusion region on a non-resizeable task.
+ updateExclusionRegion();
+ }
closeDragResizeListener();
return;
}
@@ -236,7 +293,8 @@
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
- mSurfaceControlTransactionSupplier);
+ mSurfaceControlTransactionSupplier,
+ mDisplayController);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -248,13 +306,58 @@
final int resize_corner = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_corner);
- // If either task geometry or position have changed, update this task's cornersListener
+ // If either task geometry or position have changed, update this task's
+ // exclusion region listener
if (mDragResizeListener.setGeometry(
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
- mCornersListener.onTaskCornersChanged(mTaskInfo.taskId, getGlobalCornersRegion());
+ updateExclusionRegion();
}
- mPositionInParent.set(mTaskInfo.positionInParent);
+
+ if (isMaximizeMenuActive()) {
+ if (!mTaskInfo.isVisible()) {
+ closeMaximizeMenu();
+ } else {
+ mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT);
+ }
+ }
+ }
+
+ private PointF calculateMaximizeMenuPosition() {
+ final PointF position = new PointF();
+ final Resources resources = mContext.getResources();
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ if (displayLayout == null) return position;
+
+ final int displayWidth = displayLayout.width();
+ final int displayHeight = displayLayout.height();
+ final int captionHeight = getCaptionHeight(mTaskInfo.getWindowingMode());
+
+ final ImageButton maximizeWindowButton =
+ mResult.mRootView.findViewById(R.id.maximize_window);
+ final int[] maximizeButtonLocation = new int[2];
+ maximizeWindowButton.getLocationInWindow(maximizeButtonLocation);
+
+ final int menuWidth = loadDimensionPixelSize(
+ resources, R.dimen.desktop_mode_maximize_menu_width);
+ final int menuHeight = loadDimensionPixelSize(
+ resources, R.dimen.desktop_mode_maximize_menu_height);
+
+ float menuLeft = (mPositionInParent.x + maximizeButtonLocation[0]);
+ float menuTop = (mPositionInParent.y + captionHeight);
+ final float menuRight = menuLeft + menuWidth;
+ final float menuBottom = menuTop + menuHeight;
+
+ // If the menu is out of screen bounds, shift it up/left as needed
+ if (menuRight > displayWidth) {
+ menuLeft = (displayWidth - menuWidth);
+ }
+ if (menuBottom > displayHeight) {
+ menuTop = (displayHeight - menuHeight);
+ }
+
+ return new PointF(menuLeft, menuTop);
}
boolean isHandleMenuActive() {
@@ -265,10 +368,15 @@
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
try {
- IconProvider provider = new IconProvider(mContext);
- mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+ final IconProvider provider = new IconProvider(mContext);
+ mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
PackageManager.ComponentInfoFlags.of(0)));
- ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+ final Resources resources = mContext.getResources();
+ final BaseIconFactory factory = new BaseIconFactory(mContext,
+ resources.getDisplayMetrics().densityDpi,
+ resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
+ mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
+ final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
@@ -289,7 +397,7 @@
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
- mResizeVeil = new ResizeVeil(mContext, mAppIcon, mTaskInfo,
+ mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo,
mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
}
@@ -335,26 +443,52 @@
}
/**
- * Create and display handle menu window
+ * Create and display maximize menu window
+ */
+ void createMaximizeMenu() {
+ mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
+ mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext,
+ calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
+ mMaximizeMenu.show();
+ }
+
+ /**
+ * Close the maximize menu window
+ */
+ void closeMaximizeMenu() {
+ if (!isMaximizeMenuActive()) return;
+ mMaximizeMenu.close();
+ mMaximizeMenu = null;
+ }
+
+ boolean isMaximizeMenuActive() {
+ return mMaximizeMenu != null;
+ }
+
+ /**
+ * Create and display handle menu window.
*/
void createHandleMenu() {
mHandleMenu = new HandleMenu.Builder(this)
- .setAppIcon(mAppIcon)
+ .setAppIcon(mAppIconBitmap)
.setAppName(mAppName)
.setOnClickListener(mOnCaptionButtonClickListener)
.setOnTouchListener(mOnCaptionTouchListener)
.setLayoutId(mRelayoutParams.mLayoutResId)
.setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
- .setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled())
+ .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
+ .setCaptionHeight(mResult.mCaptionHeight)
.build();
+ mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
/**
- * Close the handle menu window
+ * Close the handle menu window.
*/
void closeHandleMenu() {
if (!isHandleMenuActive()) return;
+ mWindowDecorViewHolder.onHandleMenuClosed();
mHandleMenu.close();
mHandleMenu = null;
}
@@ -362,6 +496,7 @@
@Override
void releaseViews() {
closeHandleMenu();
+ closeMaximizeMenu();
super.releaseViews();
}
@@ -387,6 +522,20 @@
}
}
+ /**
+ * Close an open maximize menu if input is outside of menu coordinates
+ *
+ * @param ev the tapped point to compare against
+ */
+ void closeMaximizeMenuIfNeeded(MotionEvent ev) {
+ if (!isMaximizeMenuActive()) return;
+
+ final PointF inputPoint = offsetCaptionLocation(ev);
+ if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+ closeMaximizeMenu();
+ }
+ }
+
boolean isFocused() {
return mTaskInfo.isFocused;
}
@@ -399,8 +548,10 @@
*/
private PointF offsetCaptionLocation(MotionEvent ev) {
final PointF result = new PointF(ev.getX(), ev.getY());
- final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
- .positionInParent;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId);
+ if (taskInfo == null) return result;
+ final Point positionInParent = taskInfo.positionInParent;
result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
result.offset(-positionInParent.x, -positionInParent.y);
return result;
@@ -426,6 +577,13 @@
}
/**
+ * Returns true if motion event is within the caption's root view's bounds.
+ */
+ boolean checkTouchEventInCaption(MotionEvent ev) {
+ return checkEventInCaptionView(ev, getCaptionViewId());
+ }
+
+ /**
* Check a passed MotionEvent if a click has occurred on any button on this caption
* Note this should only be called when a regular onClick is not possible
* (i.e. the button was clicked through status bar layer)
@@ -440,6 +598,7 @@
clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
} else {
mHandleMenu.checkClickEvent(ev);
+ closeHandleMenuIfNeeded(ev);
}
}
@@ -460,27 +619,43 @@
public void close() {
closeDragResizeListener();
closeHandleMenu();
- mCornersListener.onTaskCornersRemoved(mTaskInfo.taskId);
+ mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
super.close();
}
- private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
- if (DesktopModeStatus.isProto1Enabled()) {
- return R.layout.desktop_mode_app_controls_window_decor;
- }
+ private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
}
+ private void updatePositionInParent() {
+ mPositionInParent.set(mTaskInfo.positionInParent);
+ }
+
+ private void updateExclusionRegion() {
+ // An outdated position in parent is one reason for this to be called; update it here.
+ updatePositionInParent();
+ mExclusionRegionListener
+ .onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion());
+ }
+
/**
- * Create a new region out of the corner rects of this task.
+ * Create a new exclusion region from the corner rects (if resizeable) and caption bounds
+ * of this task.
*/
- Region getGlobalCornersRegion() {
- Region cornersRegion = mDragResizeListener.getCornersRegion();
- cornersRegion.translate(mPositionInParent.x, mPositionInParent.y);
- return cornersRegion;
+ private Region getGlobalExclusionRegion() {
+ Region exclusionRegion;
+ if (mTaskInfo.isResizeable) {
+ exclusionRegion = mDragResizeListener.getCornersRegion();
+ } else {
+ exclusionRegion = new Region();
+ }
+ exclusionRegion.union(new Rect(0, 0, mResult.mWidth,
+ getCaptionHeight(mTaskInfo.getWindowingMode())));
+ exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y);
+ return exclusionRegion;
}
/**
@@ -494,8 +669,19 @@
}
@Override
- int getCaptionHeightId() {
- return R.dimen.freeform_decor_caption_height;
+ int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return windowingMode == WINDOWING_MODE_FULLSCREEN
+ ? R.dimen.desktop_mode_fullscreen_decor_caption_height
+ : R.dimen.desktop_mode_freeform_decor_caption_height;
+ }
+
+ private int getCaptionHeight(@WindowingMode int windowingMode) {
+ return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
+ }
+
+ @Override
+ int getCaptionViewId() {
+ return R.id.desktop_mode_caption;
}
/**
@@ -522,6 +708,17 @@
mRelayoutBlock++;
}
+ @Override
+ public String toString() {
+ return "{"
+ + "mPositionInParent=" + mPositionInParent + ", "
+ + "mRelayoutBlock=" + mRelayoutBlock + ", "
+ + "taskId=" + mTaskInfo.taskId + ", "
+ + "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ + "isFocused=" + isFocused()
+ + "}";
+ }
+
static class Factory {
DesktopModeWindowDecoration create(
@@ -532,25 +729,34 @@
SurfaceControl taskSurface,
Handler handler,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ final Configuration windowDecorConfig =
+ DesktopTasksController.isDesktopDensityOverrideSet()
+ ? context.getResources().getConfiguration() // Use system context
+ : taskInfo.configuration; // Use task configuration
return new DesktopModeWindowDecoration(
context,
displayController,
taskOrganizer,
taskInfo,
taskSurface,
+ windowDecorConfig,
handler,
choreographer,
- syncQueue);
+ syncQueue,
+ rootTaskDisplayAreaOrganizer);
}
}
- interface TaskCornersListener {
- /** Inform the implementing class of this task's change in corner resize handles */
- void onTaskCornersChanged(int taskId, Region corner);
+ interface ExclusionRegionListener {
+ /** Inform the implementing class of this task's change in region resize handles */
+ void onExclusionRegionChanged(int taskId, Region region);
- /** Inform the implementing class that this task no longer needs its corners tracked,
- * likely due to it closing. */
- void onTaskCornersRemoved(int taskId);
+ /**
+ * Inform the implementing class that this task no longer needs an exclusion region,
+ * likely due to it closing.
+ */
+ void onExclusionRegionDismissed(int taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 1669cf4..8ce2d6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -40,8 +40,9 @@
* {@code 0} to indicate it's a move
* @param x x coordinate in window decoration coordinate system where the drag starts
* @param y y coordinate in window decoration coordinate system where the drag starts
+ * @return the starting task bounds
*/
- void onDragPositioningStart(@CtrlType int ctrlType, float x, float y);
+ Rect onDragPositioningStart(@CtrlType int ctrlType, float x, float y);
/**
* Called when the pointer moves during a drag-resize or drag-move.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index e32bd42..cb0a6c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -143,6 +143,24 @@
}
/**
+ * Calculates the new position of the top edge of the task and returns true if it is below the
+ * disallowed area.
+ *
+ * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
+ * should not finalize the bounds using WCT#setBounds
+ * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
+ * @param repositionStartPoint initial input coordinate
+ * @param y the y position of the motion event
+ * @return true if the top of the task is below the disallowed area
+ */
+ static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
+ Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
+ final float deltaY = y - repositionStartPoint.y;
+ final float topPosition = taskBoundsAtDragStart.top + deltaY;
+ return topPosition > disallowedAreaForEndBoundsHeight;
+ }
+
+ /**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
* the bounds are outside of the stable bounds, they are shifted to place task at the top of the
* stable bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7c6fb99..8511a21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -49,7 +50,8 @@
import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
-import com.android.internal.view.BaseIWindow;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import java.util.function.Supplier;
@@ -62,13 +64,16 @@
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
+ private final Context mContext;
private final Handler mHandler;
private final Choreographer mChoreographer;
private final InputManager mInputManager;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
- private final BaseIWindow mFakeWindow;
+
+ private final IBinder mClientToken;
+
private final IBinder mFocusGrantToken;
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
@@ -76,8 +81,9 @@
private final DragPositioningCallback mCallback;
private final SurfaceControl mInputSinkSurface;
- private final BaseIWindow mFakeSinkWindow;
+ private final IBinder mSinkClientToken;
private final InputChannel mSinkInputChannel;
+ private final DisplayController mDisplayController;
private int mTaskWidth;
private int mTaskHeight;
@@ -92,6 +98,7 @@
private int mDragPointerId = -1;
private DragDetector mDragDetector;
+ private final Region mTouchRegion = new Region();
DragResizeInputListener(
Context context,
@@ -102,25 +109,25 @@
SurfaceControl decorationSurface,
DragPositioningCallback callback,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
- Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ DisplayController displayController) {
mInputManager = context.getSystemService(InputManager.class);
+ mContext = context;
mHandler = handler;
mChoreographer = choreographer;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
mTaskCornerRadius = taskCornerRadius;
mDecorationSurface = decorationSurface;
- // Use a fake window as the backing surface is a container layer, and we don't want to
- // create a buffer layer for it, so we can't use ViewRootImpl.
- mFakeWindow = new BaseIWindow();
- mFakeWindow.setSession(mWindowSession);
+ mDisplayController = displayController;
+ mClientToken = new Binder();
mFocusGrantToken = new Binder();
mInputChannel = new InputChannel();
try {
mWindowSession.grantInputChannel(
mDisplayId,
mDecorationSurface,
- mFakeWindow,
+ mClientToken,
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
@@ -149,13 +156,13 @@
.setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
.show(mInputSinkSurface)
.apply();
- mFakeSinkWindow = new BaseIWindow();
+ mSinkClientToken = new Binder();
mSinkInputChannel = new InputChannel();
try {
mWindowSession.grantInputChannel(
mDisplayId,
mInputSinkSurface,
- mFakeSinkWindow,
+ mSinkClientToken,
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
0 /* privateFlags */,
@@ -195,34 +202,34 @@
mCornerSize = cornerSize;
mDragDetector.setTouchSlop(touchSlop);
- Region touchRegion = new Region();
+ mTouchRegion.setEmpty();
final Rect topInputBounds = new Rect(
-mResizeHandleThickness,
-mResizeHandleThickness,
mTaskWidth + mResizeHandleThickness,
0);
- touchRegion.union(topInputBounds);
+ mTouchRegion.union(topInputBounds);
final Rect leftInputBounds = new Rect(
-mResizeHandleThickness,
0,
0,
mTaskHeight);
- touchRegion.union(leftInputBounds);
+ mTouchRegion.union(leftInputBounds);
final Rect rightInputBounds = new Rect(
mTaskWidth,
0,
mTaskWidth + mResizeHandleThickness,
mTaskHeight);
- touchRegion.union(rightInputBounds);
+ mTouchRegion.union(rightInputBounds);
final Rect bottomInputBounds = new Rect(
-mResizeHandleThickness,
mTaskHeight,
mTaskWidth + mResizeHandleThickness,
mTaskHeight + mResizeHandleThickness);
- touchRegion.union(bottomInputBounds);
+ mTouchRegion.union(bottomInputBounds);
// Set up touch areas in each corner.
int cornerRadius = mCornerSize / 2;
@@ -231,28 +238,28 @@
-cornerRadius,
cornerRadius,
cornerRadius);
- touchRegion.union(mLeftTopCornerBounds);
+ mTouchRegion.union(mLeftTopCornerBounds);
mRightTopCornerBounds = new Rect(
mTaskWidth - cornerRadius,
-cornerRadius,
mTaskWidth + cornerRadius,
cornerRadius);
- touchRegion.union(mRightTopCornerBounds);
+ mTouchRegion.union(mRightTopCornerBounds);
mLeftBottomCornerBounds = new Rect(
-cornerRadius,
mTaskHeight - cornerRadius,
cornerRadius,
mTaskHeight + cornerRadius);
- touchRegion.union(mLeftBottomCornerBounds);
+ mTouchRegion.union(mLeftBottomCornerBounds);
mRightBottomCornerBounds = new Rect(
mTaskWidth - cornerRadius,
mTaskHeight - cornerRadius,
mTaskWidth + cornerRadius,
mTaskHeight + cornerRadius);
- touchRegion.union(mRightBottomCornerBounds);
+ mTouchRegion.union(mRightBottomCornerBounds);
try {
mWindowSession.updateInputChannel(
@@ -262,7 +269,7 @@
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
INPUT_FEATURE_SPY,
- touchRegion);
+ mTouchRegion);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -281,19 +288,8 @@
// issue. However, were there touchscreen-only a region out of the task bounds, mouse
// gestures will become no-op in that region, even though the mouse gestures may appear to
// be performed on the input window behind the resize handle.
- touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
- try {
- mWindowSession.updateInputChannel(
- mSinkInputChannel.getToken(),
- mDisplayId,
- mInputSinkSurface,
- FLAG_NOT_FOCUSABLE,
- 0 /* privateFlags */,
- INPUT_FEATURE_NO_INPUT_CHANNEL,
- touchRegion);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ mTouchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+ updateSinkInputChannel(mTouchRegion);
return true;
}
@@ -309,19 +305,34 @@
return region;
}
+ private void updateSinkInputChannel(Region region) {
+ try {
+ mWindowSession.updateInputChannel(
+ mSinkInputChannel.getToken(),
+ mDisplayId,
+ mInputSinkSurface,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
+ region);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+
@Override
public void close() {
mInputEventReceiver.dispose();
mInputChannel.dispose();
try {
- mWindowSession.remove(mFakeWindow);
+ mWindowSession.remove(mClientToken);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
mSinkInputChannel.dispose();
try {
- mWindowSession.remove(mFakeSinkWindow);
+ mWindowSession.remove(mSinkClientToken);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -337,6 +348,7 @@
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
+ private Rect mDragStartTaskBounds;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -398,12 +410,15 @@
}
if (mShouldHandleEvents) {
mInputManager.pilferPointers(mInputChannel.getToken());
-
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
int ctrlType = calculateCtrlType(isTouch, x, y);
- mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
+ mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
+ rawX, rawY);
+ // Increase the input sink region to cover the whole screen; this is to
+ // prevent input and focus from going to other tasks during a drag resize.
+ updateInputSinkRegionForDrag(mDragStartTaskBounds);
result = true;
}
break;
@@ -415,7 +430,8 @@
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- mCallback.onDragPositioningMove(rawX, rawY);
+ final Rect taskBounds = mCallback.onDragPositioningMove(rawX, rawY);
+ updateInputSinkRegionForDrag(taskBounds);
result = true;
break;
}
@@ -423,8 +439,13 @@
case MotionEvent.ACTION_CANCEL: {
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragPositioningEnd(
+ final Rect taskBounds = mCallback.onDragPositioningEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ // If taskBounds has changed, setGeometry will be called and update the
+ // sink region. Otherwise, we should revert it here.
+ if (taskBounds.equals(mDragStartTaskBounds)) {
+ updateSinkInputChannel(mTouchRegion);
+ }
}
mShouldHandleEvents = false;
mDragPointerId = -1;
@@ -433,7 +454,9 @@
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
- updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+ updateCursorType(e.getDisplayId(), e.getDeviceId(),
+ e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(),
+ e.getYCursorPosition());
result = true;
break;
}
@@ -444,6 +467,18 @@
return result;
}
+ private void updateInputSinkRegionForDrag(Rect taskBounds) {
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
+ final Region dragTouchRegion = new Region(-taskBounds.left,
+ -taskBounds.top,
+ -taskBounds.left + layout.width(),
+ -taskBounds.top + layout.height());
+ // Remove the localized task bounds from the touch region.
+ taskBounds.offsetTo(0, 0);
+ dragTouchRegion.op(taskBounds, Region.Op.DIFFERENCE);
+ updateSinkInputChannel(dragTouchRegion);
+ }
+
private boolean isInCornerBounds(float xf, float yf) {
return calculateCornersCtrlType(xf, yf) != 0;
}
@@ -549,7 +584,8 @@
return 0;
}
- private void updateCursorType(float x, float y) {
+ private void updateCursorType(int displayId, int deviceId, int pointerId, float x,
+ float y) {
@DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
@@ -581,9 +617,14 @@
// where views in the task can receive input events because we can't set touch regions
// of input sinks to have rounded corners.
if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
- mInputManager.setPointerIconType(cursorType);
+ if (enablePointerChoreographer()) {
+ mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
+ displayId, deviceId, pointerId, mInputChannel.getToken());
+ } else {
+ mInputManager.setPointerIconType(cursorType);
+ }
mLastCursorType = cursorType;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index e0ee252..3a1ea0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -18,6 +18,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
@@ -45,6 +46,7 @@
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
@@ -66,7 +68,7 @@
}
@Override
- public void onDragPositioningStart(int ctrlType, float x, float y) {
+ public Rect onDragPositioningStart(int ctrlType, float x, float y) {
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
@@ -78,10 +80,14 @@
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
+ return new Rect(mRepositionTaskBounds);
}
@Override
@@ -125,7 +131,9 @@
}
mTaskOrganizer.applyTransaction(wct);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && y > mDisallowedAreaForEndBoundsHeight) {
+ && DragPositioningCallbackUtility.isBelowDisallowedArea(
+ mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
+ y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index ac4a597..652a2ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -26,9 +26,12 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.PointF;
-import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -39,7 +42,6 @@
import android.window.SurfaceSyncGroup;
import com.android.wm.shell.R;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
/**
* Handle menu opened when the appropriate button is clicked on.
@@ -53,14 +55,10 @@
private static final String TAG = "HandleMenu";
private final Context mContext;
private final WindowDecoration mParentDecor;
- private WindowDecoration.AdditionalWindow mAppInfoPill;
- private WindowDecoration.AdditionalWindow mWindowingPill;
- private WindowDecoration.AdditionalWindow mMoreActionsPill;
- private final PointF mAppInfoPillPosition = new PointF();
- private final PointF mWindowingPillPosition = new PointF();
- private final PointF mMoreActionsPillPosition = new PointF();
+ private WindowDecoration.AdditionalWindow mHandleMenuWindow;
+ private final PointF mHandleMenuPosition = new PointF();
private final boolean mShouldShowWindowingPill;
- private final Drawable mAppIcon;
+ private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
private final View.OnTouchListener mOnTouchListener;
@@ -70,18 +68,16 @@
private final int mCaptionY;
private int mMarginMenuTop;
private int mMarginMenuStart;
- private int mMarginMenuSpacing;
+ private int mMenuHeight;
private int mMenuWidth;
- private int mAppInfoPillHeight;
- private int mWindowingPillHeight;
- private int mMoreActionsPillHeight;
- private int mShadowRadius;
- private int mCornerRadius;
+ private final int mCaptionHeight;
+ private HandleMenuAnimator mHandleMenuAnimator;
HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
+ Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
+ int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -90,9 +86,10 @@
mCaptionY = captionY;
mOnClickListener = onClickListener;
mOnTouchListener = onTouchListener;
- mAppIcon = appIcon;
+ mAppIconBitmap = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
+ mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
}
@@ -101,98 +98,125 @@
final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- createAppInfoPill(t, ssg);
- if (mShouldShowWindowingPill) {
- createWindowingPill(t, ssg);
- }
- createMoreActionsPill(t, ssg);
+ createHandleMenuWindow(t, ssg);
ssg.addTransaction(t);
ssg.markSyncReady();
setupHandleMenu();
+ animateHandleMenu();
}
- private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
- final int x = (int) mAppInfoPillPosition.x;
- final int y = (int) mAppInfoPillPosition.y;
- mAppInfoPill = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
- "Menu's app info pill",
- t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
- }
-
- private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
- final int x = (int) mWindowingPillPosition.x;
- final int y = (int) mWindowingPillPosition.y;
- mWindowingPill = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
- "Menu's windowing pill",
- t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
- }
-
- private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
- final int x = (int) mMoreActionsPillPosition.x;
- final int y = (int) mMoreActionsPillPosition.y;
- mMoreActionsPill = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
- "Menu's more actions pill",
- t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+ private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+ final int x = (int) mHandleMenuPosition.x;
+ final int y = (int) mHandleMenuPosition.y;
+ mHandleMenuWindow = mParentDecor.addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
+ t, ssg, x, y, mMenuWidth, mMenuHeight);
+ final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
+ mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
}
/**
- * Set up interactive elements and color of this handle menu
+ * Animates the appearance of the handle menu and its three pills.
+ */
+ private void animateHandleMenu() {
+ if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ mHandleMenuAnimator.animateCaptionHandleExpandToOpen();
+ } else {
+ mHandleMenuAnimator.animateOpen();
+ }
+ }
+
+ /**
+ * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
+ * pill.
*/
private void setupHandleMenu() {
- // App Info pill setup.
- final View appInfoPillView = mAppInfoPill.mWindowViewHost.getView();
- final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
- final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
- final TextView appName = appInfoPillView.findViewById(R.id.application_name);
- collapseBtn.setOnClickListener(mOnClickListener);
- appInfoPillView.setOnTouchListener(mOnTouchListener);
- appIcon.setImageDrawable(mAppIcon);
- appName.setText(mAppName);
-
- // Windowing pill setup.
+ final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
+ handleMenu.setOnTouchListener(mOnTouchListener);
+ setupAppInfoPill(handleMenu);
if (mShouldShowWindowingPill) {
- final View windowingPillView = mWindowingPill.mWindowViewHost.getView();
- final ImageButton fullscreenBtn = windowingPillView.findViewById(
- R.id.fullscreen_button);
- final ImageButton splitscreenBtn = windowingPillView.findViewById(
- R.id.split_screen_button);
- final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
- final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
- fullscreenBtn.setOnClickListener(mOnClickListener);
- splitscreenBtn.setOnClickListener(mOnClickListener);
- floatingBtn.setOnClickListener(mOnClickListener);
- desktopBtn.setOnClickListener(mOnClickListener);
- // The button corresponding to the windowing mode that the task is currently in uses a
- // different color than the others.
- final ColorStateList activeColorStateList = ColorStateList.valueOf(
- mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
- final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
- mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
- fullscreenBtn.setImageTintList(
- mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- ? activeColorStateList : inActiveColorStateList);
- splitscreenBtn.setImageTintList(
- mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
- ? activeColorStateList : inActiveColorStateList);
- floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
- ? activeColorStateList : inActiveColorStateList);
- desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- ? activeColorStateList : inActiveColorStateList);
+ setupWindowingPill(handleMenu);
}
-
- // More Actions pill setup.
- final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
- final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
- closeBtn.setOnClickListener(mOnClickListener);
- final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
- selectBtn.setOnClickListener(mOnClickListener);
+ setupMoreActionsPill(handleMenu);
}
/**
- * Updates the handle menu pills' position variables to reflect their next positions
+ * Set up interactive elements of handle menu's app info pill.
+ */
+ private void setupAppInfoPill(View handleMenu) {
+ final ImageButton collapseBtn = handleMenu.findViewById(R.id.collapse_menu_button);
+ final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
+ final TextView appName = handleMenu.findViewById(R.id.application_name);
+ collapseBtn.setOnClickListener(mOnClickListener);
+ appIcon.setImageBitmap(mAppIconBitmap);
+ appName.setText(mAppName);
+ }
+
+ /**
+ * Set up interactive elements and color of handle menu's windowing pill.
+ */
+ private void setupWindowingPill(View handleMenu) {
+ final ImageButton fullscreenBtn = handleMenu.findViewById(
+ R.id.fullscreen_button);
+ final ImageButton splitscreenBtn = handleMenu.findViewById(
+ R.id.split_screen_button);
+ final ImageButton floatingBtn = handleMenu.findViewById(R.id.floating_button);
+ // TODO: Remove once implemented.
+ floatingBtn.setVisibility(View.GONE);
+
+ final ImageButton desktopBtn = handleMenu.findViewById(R.id.desktop_button);
+ fullscreenBtn.setOnClickListener(mOnClickListener);
+ splitscreenBtn.setOnClickListener(mOnClickListener);
+ floatingBtn.setOnClickListener(mOnClickListener);
+ desktopBtn.setOnClickListener(mOnClickListener);
+ // The button corresponding to the windowing mode that the task is currently in uses a
+ // different color than the others.
+ final ColorStateList[] iconColors = getWindowingIconColor();
+ final ColorStateList inActiveColorStateList = iconColors[0];
+ final ColorStateList activeColorStateList = iconColors[1];
+ final int windowingMode = mTaskInfo.getWindowingMode();
+ fullscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_FULLSCREEN
+ ? activeColorStateList : inActiveColorStateList);
+ splitscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ ? activeColorStateList : inActiveColorStateList);
+ floatingBtn.setImageTintList(windowingMode == WINDOWING_MODE_PINNED
+ ? activeColorStateList : inActiveColorStateList);
+ desktopBtn.setImageTintList(windowingMode == WINDOWING_MODE_FREEFORM
+ ? activeColorStateList : inActiveColorStateList);
+ }
+
+ /**
+ * Set up interactive elements & height of handle menu's more actions pill
+ */
+ private void setupMoreActionsPill(View handleMenu) {
+ final Button selectBtn = handleMenu.findViewById(R.id.select_button);
+ selectBtn.setOnClickListener(mOnClickListener);
+ final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
+ // TODO: Remove once implemented.
+ screenshotBtn.setVisibility(View.GONE);
+ }
+
+ /**
+ * Returns array of windowing icon color based on current UI theme. First element of the
+ * array is for inactive icons and the second is for active icons.
+ */
+ private ColorStateList[] getWindowingIconColor() {
+ final int mode = mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
+ final TypedArray typedArray = mContext.obtainStyledAttributes(new int[]{
+ com.android.internal.R.attr.materialColorOnSurface,
+ com.android.internal.R.attr.materialColorPrimary});
+ final int inActiveColor = typedArray.getColor(0, isNightMode ? Color.WHITE : Color.BLACK);
+ final int activeColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
+ typedArray.recycle();
+ return new ColorStateList[]{ColorStateList.valueOf(inActiveColor),
+ ColorStateList.valueOf(activeColor)};
+ }
+
+ /**
+ * Updates handle menu's position variables to reflect its next position.
*/
private void updateHandleMenuPillPositions() {
final int menuX, menuY;
@@ -209,55 +233,36 @@
menuY = mCaptionY + mMarginMenuStart;
}
- // App Info pill setup.
- final int appInfoPillY = menuY;
- mAppInfoPillPosition.set(menuX, appInfoPillY);
+ // Handle Menu position setup.
+ mHandleMenuPosition.set(menuX, menuY);
- final int windowingPillY, moreActionsPillY;
- if (mShouldShowWindowingPill) {
- windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
- mWindowingPillPosition.set(menuX, windowingPillY);
- moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
- mMoreActionsPillPosition.set(menuX, moreActionsPillY);
- } else {
- // Just start after the end of the app info pill + margins.
- moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
- mMoreActionsPillPosition.set(menuX, moreActionsPillY);
- }
}
/**
* Update pill layout, in case task changes have caused positioning to change.
- * @param t
*/
void relayout(SurfaceControl.Transaction t) {
- if (mAppInfoPill != null) {
+ if (mHandleMenuWindow != null) {
updateHandleMenuPillPositions();
- t.setPosition(mAppInfoPill.mWindowSurface,
- mAppInfoPillPosition.x, mAppInfoPillPosition.y);
- // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
- final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
- if (shouldShowWindowingPill) {
- t.setPosition(mWindowingPill.mWindowSurface,
- mWindowingPillPosition.x, mWindowingPillPosition.y);
- }
- t.setPosition(mMoreActionsPill.mWindowSurface,
- mMoreActionsPillPosition.x, mMoreActionsPillPosition.y);
+ t.setPosition(mHandleMenuWindow.mWindowSurface,
+ mHandleMenuPosition.x, mHandleMenuPosition.y);
}
}
+
/**
* Check a passed MotionEvent if a click has occurred on any button on this caption
* Note this should only be called when a regular onClick is not possible
* (i.e. the button was clicked through status bar layer)
+ *
* @param ev the MotionEvent to compare against.
*/
void checkClickEvent(MotionEvent ev) {
- final View appInfoPill = mAppInfoPill.mWindowViewHost.getView();
- final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
+ final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
+ final ImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
// Translate the input point from display coordinates to the same space as the collapse
// button, meaning its parent (app info pill view).
- final PointF inputPoint = new PointF(ev.getX() - mAppInfoPillPosition.x,
- ev.getY() - mAppInfoPillPosition.y);
+ final PointF inputPoint = new PointF(ev.getX() - mHandleMenuPosition.x,
+ ev.getY() - mHandleMenuPosition.y);
if (pointInView(collapse, inputPoint.x, inputPoint.y)) {
mOnClickListener.onClick(collapse);
}
@@ -267,27 +272,15 @@
* A valid menu input is one of the following:
* An input that happens in the menu views.
* Any input before the views have been laid out.
+ *
* @param inputPoint the input to compare against.
*/
boolean isValidMenuInput(PointF inputPoint) {
if (!viewsLaidOut()) return true;
- final boolean pointInAppInfoPill = pointInView(
- mAppInfoPill.mWindowViewHost.getView(),
- inputPoint.x - mAppInfoPillPosition.x,
- inputPoint.y - mAppInfoPillPosition.y);
- boolean pointInWindowingPill = false;
- if (mWindowingPill != null) {
- pointInWindowingPill = pointInView(
- mWindowingPill.mWindowViewHost.getView(),
- inputPoint.x - mWindowingPillPosition.x,
- inputPoint.y - mWindowingPillPosition.y);
- }
- final boolean pointInMoreActionsPill = pointInView(
- mMoreActionsPill.mWindowViewHost.getView(),
- inputPoint.x - mMoreActionsPillPosition.x,
- inputPoint.y - mMoreActionsPillPosition.y);
-
- return pointInAppInfoPill || pointInWindowingPill || pointInMoreActionsPill;
+ return pointInView(
+ mHandleMenuWindow.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuPosition.x,
+ inputPoint.y - mHandleMenuPosition.y);
}
private boolean pointInView(View v, float x, float y) {
@@ -297,33 +290,33 @@
/**
* Check if the views for handle menu can be seen.
- * @return
*/
private boolean viewsLaidOut() {
- return mAppInfoPill.mWindowViewHost.getView().isLaidOut();
+ return mHandleMenuWindow.mWindowViewHost.getView().isLaidOut();
}
-
private void loadHandleMenuDimensions() {
final Resources resources = mContext.getResources();
mMenuWidth = loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_width);
+ mMenuHeight = getHandleMenuHeight(resources);
mMarginMenuTop = loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_margin_top);
mMarginMenuStart = loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_margin_start);
- mMarginMenuSpacing = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
- mAppInfoPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_app_info_pill_height);
- mWindowingPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_windowing_pill_height);
- mMoreActionsPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
- mShadowRadius = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_shadow_radius);
- mCornerRadius = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_corner_radius);
+ }
+
+ /**
+ * Determines handle menu height based on if windowing pill should be shown.
+ */
+ private int getHandleMenuHeight(Resources resources) {
+ int menuHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_height);
+ if (!mShouldShowWindowingPill) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height);
+ }
+ return menuHeight;
}
private int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -334,26 +327,29 @@
}
void close() {
- mAppInfoPill.releaseView();
- mAppInfoPill = null;
- if (mWindowingPill != null) {
- mWindowingPill.releaseView();
- mWindowingPill = null;
+ final Runnable after = () -> {
+ mHandleMenuWindow.releaseView();
+ mHandleMenuWindow = null;
+ };
+ if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ mHandleMenuAnimator.animateCollapseIntoHandleClose(after);
+ } else {
+ mHandleMenuAnimator.animateClose(after);
}
- mMoreActionsPill.releaseView();
- mMoreActionsPill = null;
}
static final class Builder {
private final WindowDecoration mParent;
private CharSequence mName;
- private Drawable mAppIcon;
+ private Bitmap mAppIcon;
private View.OnClickListener mOnClickListener;
private View.OnTouchListener mOnTouchListener;
private int mLayoutId;
private int mCaptionX;
private int mCaptionY;
private boolean mShowWindowingPill;
+ private int mCaptionHeight;
Builder(@NonNull WindowDecoration parent) {
@@ -365,7 +361,7 @@
return this;
}
- Builder setAppIcon(@Nullable Drawable appIcon) {
+ Builder setAppIcon(@Nullable Bitmap appIcon) {
mAppIcon = appIcon;
return this;
}
@@ -396,9 +392,14 @@
return this;
}
+ Builder setCaptionHeight(int captionHeight) {
+ mCaptionHeight = captionHeight;
+ return this;
+ }
+
HandleMenu build() {
return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
- mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
+ mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
new file mode 100644
index 0000000..8c5d4a2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2023 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.windowdecor
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.View.ALPHA
+import android.view.View.SCALE_X
+import android.view.View.SCALE_Y
+import android.view.View.TRANSLATION_Y
+import android.view.View.TRANSLATION_Z
+import android.view.ViewGroup
+import androidx.core.animation.doOnEnd
+import androidx.core.view.children
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
+
+/** Animates the Handle Menu opening. */
+class HandleMenuAnimator(
+ private val handleMenu: View,
+ private val menuWidth: Int,
+ private val captionHeight: Float
+) {
+ companion object {
+ // Open animation constants
+ private const val MENU_Y_TRANSLATION_OPEN_DURATION: Long = 150
+ private const val HEADER_NONFREEFORM_SCALE_OPEN_DURATION: Long = 150
+ private const val HEADER_FREEFORM_SCALE_OPEN_DURATION: Long = 217
+ private const val HEADER_ELEVATION_OPEN_DURATION: Long = 83
+ private const val HEADER_CONTENT_ALPHA_OPEN_DURATION: Long = 100
+ private const val BODY_SCALE_OPEN_DURATION: Long = 180
+ private const val BODY_ALPHA_OPEN_DURATION: Long = 150
+ private const val BODY_ELEVATION_OPEN_DURATION: Long = 83
+ private const val BODY_CONTENT_ALPHA_OPEN_DURATION: Long = 167
+
+ private const val ELEVATION_OPEN_DELAY: Long = 33
+ private const val HEADER_CONTENT_ALPHA_OPEN_DELAY: Long = 67
+ private const val BODY_SCALE_OPEN_DELAY: Long = 50
+ private const val BODY_ALPHA_OPEN_DELAY: Long = 133
+
+ private const val HALF_INITIAL_SCALE: Float = 0.5f
+ private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f
+ private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f
+
+ // Close animation constants
+ private const val HEADER_CLOSE_DELAY: Long = 20
+ private const val HEADER_CLOSE_DURATION: Long = 50
+ private const val HEADER_CONTENT_OPACITY_CLOSE_DELAY: Long = 25
+ private const val HEADER_CONTENT_OPACITY_CLOSE_DURATION: Long = 25
+ private const val BODY_CLOSE_DURATION: Long = 50
+ }
+
+ private val animators: MutableList<Animator> = mutableListOf()
+ private var runningAnimation: AnimatorSet? = null
+
+ private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
+ private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
+ private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+
+ /** Animates the opening of the handle menu. */
+ fun animateOpen() {
+ prepareMenuForAnimation()
+ appInfoPillExpand()
+ animateAppInfoPillOpen()
+ animateWindowingPillOpen()
+ animateMoreActionsPillOpen()
+ runAnimations()
+ }
+
+ /**
+ * Animates the opening of the handle menu. The caption handle in full screen and split screen
+ * will expand until it assumes the shape of the app info pill. Then, the other two pills will
+ * appear.
+ */
+ fun animateCaptionHandleExpandToOpen() {
+ prepareMenuForAnimation()
+ captionHandleExpandIntoAppInfoPill()
+ animateAppInfoPillOpen()
+ animateWindowingPillOpen()
+ animateMoreActionsPillOpen()
+ runAnimations()
+ }
+
+ /**
+ * Animates the closing of the handle menu. The windowing and more actions pill vanish. Then,
+ * the app info pill will collapse into the shape of the caption handle in full screen and split
+ * screen.
+ *
+ * @param after runs after the animation finishes.
+ */
+ fun animateCollapseIntoHandleClose(after: Runnable) {
+ appInfoCollapseToHandle()
+ animateAppInfoPillFadeOut()
+ windowingPillClose()
+ moreActionsPillClose()
+ runAnimations(after)
+ }
+
+ /**
+ * Animates the closing of the handle menu. The windowing and more actions pill vanish. Then,
+ * the app info pill will collapse into the shape of the caption handle in full screen and split
+ * screen.
+ *
+ * @param after runs after animation finishes.
+ *
+ */
+ fun animateClose(after: Runnable) {
+ appInfoPillCollapse()
+ animateAppInfoPillFadeOut()
+ windowingPillClose()
+ moreActionsPillClose()
+ runAnimations(after)
+ }
+
+ /**
+ * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
+ * Presets pivots of handle menu and body pills for scaling animation.
+ */
+ private fun prepareMenuForAnimation() {
+ // Preset opacity
+ appInfoPill.children.forEach { it.alpha = 0f }
+ windowingPill.alpha = 0f
+ moreActionsPill.alpha = 0f
+
+ // Setup pivots.
+ handleMenu.pivotX = menuWidth / 2f
+ handleMenu.pivotY = 0f
+
+ windowingPill.pivotX = menuWidth / 2f
+ windowingPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+ moreActionsPill.pivotX = menuWidth / 2f
+ moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+ }
+
+ private fun animateAppInfoPillOpen() {
+ // Header Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = HEADER_ELEVATION_OPEN_DURATION
+ }
+
+ // Content Opacity Animation
+ appInfoPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = HEADER_CONTENT_ALPHA_OPEN_DELAY
+ duration = HEADER_CONTENT_ALPHA_OPEN_DURATION
+ }
+ }
+ }
+
+ private fun captionHandleExpandIntoAppInfoPill() {
+ // Header scaling animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
+ .apply { duration = HEADER_NONFREEFORM_SCALE_OPEN_DURATION }
+
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
+ .apply { duration = HEADER_NONFREEFORM_SCALE_OPEN_DURATION }
+
+ // Downward y-translation animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
+ duration = MENU_Y_TRANSLATION_OPEN_DURATION
+ }
+ }
+
+ private fun appInfoPillExpand() {
+ // Header scaling animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ duration = HEADER_FREEFORM_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ duration = HEADER_FREEFORM_SCALE_OPEN_DURATION
+ }
+ }
+
+ private fun animateWindowingPillOpen() {
+ // Windowing X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ // Windowing Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_ALPHA_OPEN_DURATION
+ }
+
+ // Windowing Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = BODY_ELEVATION_OPEN_DURATION
+ }
+
+ // Windowing Content Opacity Animation
+ windowingPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_CONTENT_ALPHA_OPEN_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun animateMoreActionsPillOpen() {
+ // More Actions X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ // More Actions Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_ALPHA_OPEN_DURATION
+ }
+
+ // More Actions Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = BODY_ELEVATION_OPEN_DURATION
+ }
+
+ // More Actions Content Opacity Animation
+ moreActionsPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_CONTENT_ALPHA_OPEN_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun appInfoPillCollapse() {
+ // Header scaling animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_X, 0f).apply {
+ startDelay = HEADER_CLOSE_DELAY
+ duration = HEADER_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, 0f).apply {
+ startDelay = HEADER_CLOSE_DELAY
+ duration = HEADER_CLOSE_DURATION
+ }
+ }
+
+ private fun appInfoCollapseToHandle() {
+ // Header X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X).apply {
+ startDelay = HEADER_CLOSE_DELAY
+ duration = HEADER_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y).apply {
+ startDelay = HEADER_CLOSE_DELAY
+ duration = HEADER_CLOSE_DURATION
+ }
+ // Upward y-translation animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Y, yStart).apply {
+ startDelay = HEADER_CLOSE_DELAY
+ duration = HEADER_CLOSE_DURATION
+ }
+ }
+
+ private fun animateAppInfoPillFadeOut() {
+ // Header Content Opacity Animation
+ appInfoPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 0f).apply {
+ startDelay = HEADER_CONTENT_OPACITY_CLOSE_DELAY
+ duration = HEADER_CONTENT_OPACITY_CLOSE_DURATION
+ }
+ }
+ }
+
+ private fun windowingPillClose() {
+ // Windowing X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // windowing Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+ }
+
+ private fun moreActionsPillClose() {
+ // More Actions X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // More Actions Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // upward more actions pill y-translation animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Y, yStart).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+ }
+
+ /**
+ * Runs the list of hide animators concurrently.
+ *
+ * @param after runs after animation finishes.
+ */
+ private fun runAnimations(after: Runnable? = null) {
+ runningAnimation?.apply {
+ // Remove all listeners, so that after runnable isn't triggered upon cancel.
+ removeAllListeners()
+ // If an animation runs while running animation is triggered, gracefully cancel.
+ cancel()
+ }
+
+ runningAnimation = AnimatorSet().apply {
+ playTogether(animators)
+ animators.clear()
+ doOnEnd {
+ after?.run()
+ runningAnimation = null
+ }
+ start()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
new file mode 100644
index 0000000..921708f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 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.windowdecor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PixelFormat
+import android.graphics.PointF
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import android.widget.Button
+import android.window.TaskConstants
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow
+import java.util.function.Supplier
+
+
+/**
+ * Menu that appears when user long clicks the maximize button. Gives the user the option to
+ * maximize the task or snap the task to the right or left half of the screen.
+ */
+class MaximizeMenu(
+ private val syncQueue: SyncTransactionQueue,
+ private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val displayController: DisplayController,
+ private val taskInfo: RunningTaskInfo,
+ private val onClickListener: OnClickListener,
+ private val decorWindowContext: Context,
+ private val menuPosition: PointF,
+ private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
+) {
+ private var maximizeMenu: AdditionalWindow? = null
+ private lateinit var viewHost: SurfaceControlViewHost
+ private lateinit var leash: SurfaceControl
+ private val shadowRadius = loadDimensionPixelSize(
+ R.dimen.desktop_mode_maximize_menu_shadow_radius
+ ).toFloat()
+ private val cornerRadius = loadDimensionPixelSize(
+ R.dimen.desktop_mode_maximize_menu_corner_radius
+ ).toFloat()
+
+ /** Position the menu relative to the caption's position. */
+ fun positionMenu(position: PointF, t: Transaction) {
+ menuPosition.set(position)
+ t.setPosition(leash, menuPosition.x, menuPosition.y)
+ }
+
+ /** Creates and shows the maximize window. */
+ fun show() {
+ if (maximizeMenu != null) return
+ createMaximizeMenu()
+ setupMaximizeMenu()
+ }
+
+ /** Closes the maximize window and releases its view. */
+ fun close() {
+ maximizeMenu?.releaseView()
+ maximizeMenu = null
+ }
+
+ /** Create a maximize menu that is attached to the display area. */
+ private fun createMaximizeMenu() {
+ val t = transactionSupplier.get()
+ val v = LayoutInflater.from(decorWindowContext).inflate(
+ R.layout.desktop_mode_window_decor_maximize_menu,
+ null // Root
+ )
+ val builder = SurfaceControl.Builder()
+ rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder)
+ leash = builder
+ .setName("Maximize Menu")
+ .setContainerLayer()
+ .build()
+ val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+ val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
+ val lp = WindowManager.LayoutParams(
+ menuWidth,
+ menuHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT
+ )
+ lp.title = "Maximize Menu for Task=" + taskInfo.taskId
+ lp.setTrustedOverlay()
+ val windowManager = WindowlessWindowManager(
+ taskInfo.configuration,
+ leash,
+ null // HostInputToken
+ )
+ viewHost = SurfaceControlViewHost(decorWindowContext,
+ displayController.getDisplay(taskInfo.displayId), windowManager,
+ "MaximizeMenu")
+ viewHost.setView(v, lp)
+
+ // Bring menu to front when open
+ t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU)
+ .setPosition(leash, menuPosition.x, menuPosition.y)
+ .setWindowCrop(leash, menuWidth, menuHeight)
+ .setShadowRadius(leash, shadowRadius)
+ .setCornerRadius(leash, cornerRadius)
+ .show(leash)
+ maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier)
+
+ syncQueue.runInSync { transaction ->
+ transaction.merge(t)
+ t.close()
+ }
+ }
+
+ private fun loadDimensionPixelSize(resourceId: Int): Int {
+ return if (resourceId == Resources.ID_NULL) {
+ 0
+ } else {
+ decorWindowContext.resources.getDimensionPixelSize(resourceId)
+ }
+ }
+
+ private fun setupMaximizeMenu() {
+ val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return
+
+ maximizeMenuView.requireViewById<Button>(
+ R.id.maximize_menu_maximize_button
+ ).setOnClickListener(onClickListener)
+ maximizeMenuView.requireViewById<Button>(
+ R.id.maximize_menu_snap_right_button
+ ).setOnClickListener(onClickListener)
+ maximizeMenuView.requireViewById<Button>(
+ R.id.maximize_menu_snap_left_button
+ ).setOnClickListener(onClickListener)
+ }
+
+ /**
+ * A valid menu input is one of the following:
+ * An input that happens in the menu views.
+ * Any input before the views have been laid out.
+ *
+ * @param inputPoint the input to compare against.
+ */
+ fun isValidMenuInput(inputPoint: PointF): Boolean {
+ val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
+ return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
+ inputPoint.y - menuPosition.y)
+ }
+
+ private fun pointInView(v: View, x: Float, y: Float): Boolean {
+ return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ /**
+ * Check if the views for maximize menu can be seen.
+ */
+ private fun viewsLaidOut(): Boolean {
+ return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index b2267dd..af05523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -2,10 +2,12 @@
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
import android.graphics.PointF
import android.graphics.Rect
import android.view.MotionEvent
import android.view.SurfaceControl
+import com.android.internal.policy.ScreenDecorationsUtils
/**
* Creates an animator to shrink and position task after a user drags a fullscreen task from
@@ -14,6 +16,7 @@
* accessed by the EnterDesktopTaskTransitionHandler.
*/
class MoveToDesktopAnimator @JvmOverloads constructor(
+ private val context: Context,
private val startBounds: Rect,
private val taskInfo: RunningTaskInfo,
private val taskSurface: SurfaceControl,
@@ -33,9 +36,11 @@
.setDuration(ANIMATION_DURATION.toLong())
.apply {
val t = SurfaceControl.Transaction()
+ val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
addUpdateListener { animation ->
val animatorValue = animation.animatedValue as Float
t.setScale(taskSurface, animatorValue, animatorValue)
+ .setCornerRadius(taskSurface, cornerRadius)
.apply()
}
}
@@ -44,19 +49,40 @@
val position: PointF = PointF(0.0f, 0.0f)
/**
+ * Whether motion events from the drag gesture should affect the dragged surface or not. Used
+ * to disallow moving the surface's position prematurely since it should not start moving at
+ * all until the drag-to-desktop transition is ready to animate and the wallpaper/home are
+ * ready to be revealed behind the dragged/scaled task.
+ */
+ private var allowSurfaceChangesOnMove = false
+
+ /**
* Starts the animation that scales the task down.
*/
fun startAnimation() {
+ allowSurfaceChangesOnMove = true
dragToDesktopAnimator.start()
}
/**
- * Uses the position of the motion event and the current scale of the task as defined by the
- * ValueAnimator to update the local position variable and set the task surface's position
+ * Uses the position of the motion event of the drag-to-desktop gesture to update the dragged
+ * task's position on screen to follow the touch point. Note that the position change won't
+ * be applied immediately always, such as near the beginning where it waits until the wallpaper
+ * or home are visible behind it. Once they're visible the surface will catch-up to the most
+ * recent touch position.
*/
fun updatePosition(ev: MotionEvent) {
- position.x = ev.x - animatedTaskWidth / 2
- position.y = ev.y
+ // Using rawX/Y because when dragging a task in split, the local X/Y is relative to the
+ // split stages, but the split task surface is re-parented to the task display area to
+ // allow dragging beyond its stage across any region of the display. Because of that, the
+ // rawX/Y are more true to where the gesture is on screen and where the surface should be
+ // positioned.
+ position.x = ev.rawX - animatedTaskWidth / 2
+ position.y = ev.rawY
+
+ if (!allowSurfaceChangesOnMove) {
+ return
+ }
val t = transactionFactory()
t.setPosition(taskSurface, position.x, position.y)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index bfce72b..368231e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -49,11 +49,13 @@
private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final Drawable mAppIcon;
+ private ImageView mIconView;
private SurfaceControl mParentSurface;
private SurfaceControl mVeilSurface;
private final RunningTaskInfo mTaskInfo;
private SurfaceControlViewHost mViewHost;
private final Display mDisplay;
+ private ValueAnimator mVeilAnimator;
public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
@@ -97,8 +99,8 @@
mViewHost = new SurfaceControlViewHost(mContext, mDisplay, windowManager, "ResizeVeil");
mViewHost.setView(v, lp);
- final ImageView appIcon = mViewHost.getView().findViewById(R.id.veil_application_icon);
- appIcon.setImageDrawable(mAppIcon);
+ mIconView = mViewHost.getView().findViewById(R.id.veil_application_icon);
+ mIconView.setImageDrawable(mAppIcon);
}
/**
@@ -123,17 +125,34 @@
relayout(taskBounds, t);
if (fadeIn) {
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(0f, 1f);
- animator.setDuration(RESIZE_ALPHA_DURATION);
- animator.addUpdateListener(animation -> {
- t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+ mVeilAnimator = new ValueAnimator();
+ mVeilAnimator.setFloatValues(0f, 1f);
+ mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
+ mVeilAnimator.addUpdateListener(animation -> {
+ t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction());
t.apply();
});
+ mVeilAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ t.setAlpha(mVeilSurface, 1);
+ t.apply();
+ }
+ });
+
+ final ValueAnimator iconAnimator = new ValueAnimator();
+ iconAnimator.setFloatValues(0f, 1f);
+ iconAnimator.setDuration(RESIZE_ALPHA_DURATION);
+ iconAnimator.addUpdateListener(animation -> {
+ mIconView.setAlpha(animation.getAnimatedFraction());
+ });
t.show(mVeilSurface)
.addTransactionCommittedListener(
- mContext.getMainExecutor(), () -> animator.start())
+ mContext.getMainExecutor(), () -> {
+ mVeilAnimator.start();
+ iconAnimator.start();
+ })
.setAlpha(mVeilSurface, 0);
} else {
// Show the veil immediately at full opacity.
@@ -172,11 +191,17 @@
/**
* Calls relayout to update task and veil bounds.
+ * Finishes veil fade in if animation is currently running; this is to prevent empty space
+ * being visible behind the transparent veil during a fast resize.
*
* @param t a transaction to be applied in sync with the veil draw.
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+ if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
+ mVeilAnimator.removeAllUpdateListeners();
+ mVeilAnimator.end();
+ }
relayout(newBounds, t);
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index c9c58de..4b55a0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -21,6 +21,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -58,6 +59,7 @@
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
@@ -83,7 +85,7 @@
}
@Override
- public void onDragPositioningStart(int ctrlType, float x, float y) {
+ public Rect onDragPositioningStart(int ctrlType, float x, float y) {
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
@@ -98,10 +100,14 @@
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mDesktopWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
+ return new Rect(mRepositionTaskBounds);
}
@Override
@@ -142,7 +148,9 @@
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
}
- } else if (y > mDisallowedAreaForEndBoundsHeight) {
+ } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
+ mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
+ y)) {
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0b0d9d5..634b755 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -17,8 +17,12 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.statusBars;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -28,6 +32,8 @@
import android.graphics.Rect;
import android.os.Binder;
import android.view.Display;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -42,6 +48,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import java.util.function.Supplier;
@@ -115,6 +122,8 @@
SurfaceControl mCaptionContainerSurface;
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
+ private Configuration mWindowDecorConfig;
+ private boolean mIsCaptionVisible;
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
@@ -125,10 +134,12 @@
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface) {
- this(context, displayController, taskOrganizer, taskInfo, taskSurface,
+ SurfaceControl taskSurface,
+ Configuration windowDecorConfig) {
+ this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
@@ -136,33 +147,26 @@
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
+ @NonNull SurfaceControl taskSurface,
+ Configuration windowDecorConfig,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
- mTaskSurface = taskSurface;
+ mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mDecorWindowContext = mContext.createConfigurationContext(
- getConfigurationWithOverrides(mTaskInfo));
- }
-
- /**
- * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
- *
- * Allows values to be overridden before returning the configuration.
- */
- protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
- return taskInfo.getConfiguration();
+ mWindowDecorConfig = windowDecorConfig;
+ mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
}
/**
@@ -179,7 +183,6 @@
RelayoutResult<T> outResult) {
outResult.reset();
- final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
@@ -198,18 +201,26 @@
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
- if (oldTaskConfig.densityDpi != taskConfig.densityDpi
+
+ final int oldDensityDpi = mWindowDecorConfig.densityDpi;
+ final int oldNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig
+ : mTaskInfo.getConfiguration();
+ final int newDensityDpi = mWindowDecorConfig.densityDpi;
+ final int newNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ if (oldDensityDpi != newDensityDpi
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId
- || oldLayoutResId != mLayoutResId) {
+ || oldLayoutResId != mLayoutResId
+ || oldNightMode != newNightMode) {
releaseViews();
if (!obtainDisplayOrRegisterListener()) {
outResult.mRootView = null;
return;
}
- mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
+ mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
+ mDecorWindowContext.setTheme(mContext.getThemeResId());
if (params.mLayoutResId != 0) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
.inflate(params.mLayoutResId, null);
@@ -221,8 +232,13 @@
.inflate(params.mLayoutResId, null);
}
+ updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+
final Resources resources = mDecorWindowContext.getResources();
+ final Configuration taskConfig = mTaskInfo.getConfiguration();
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN;
outResult.mWidth = taskBounds.width();
outResult.mHeight = taskBounds.height();
@@ -253,10 +269,10 @@
.build();
}
- final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
@@ -265,33 +281,60 @@
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
- wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
- wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
- mCaptionInsetsRect);
+ if (mIsCaptionVisible) {
+ mCaptionInsetsRect.bottom =
+ mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+ mCaptionInsetsRect);
+ } else {
+ wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+ WindowInsets.Type.captionBar());
+ wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+ WindowInsets.Type.mandatorySystemGestures());
+ }
} else {
startT.hide(mCaptionContainerSurface);
}
// Task surface itself
- float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
- int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
- mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
- mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
- mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
- startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
- .setShadowRadius(mTaskSurface, shadowRadius)
- .setColor(mTaskSurface, mTmpColor)
+ if (isFullscreen) {
+ // Setting the task crop to the width/height stops input events from being sent to
+ // some regions of the app window. See b/300324920
+ // TODO(b/296921174): investigate whether crop/position needs to be set by window
+ // decorations at all when transition handlers are already taking ownership of the task
+ // surface placement/crop, especially when in fullscreen where tasks cannot be
+ // drag-resized by the window decoration.
+ startT.setWindowCrop(mTaskSurface, null);
+ finishT.setWindowCrop(mTaskSurface, null);
+ // Shadow is not needed for fullscreen tasks
+ shadowRadius = 0;
+ } else {
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+ }
+ startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setShadowRadius(mTaskSurface, shadowRadius)
- .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ .setShadowRadius(mTaskSurface, shadowRadius);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ // When fluid resize is enabled, add a background to freeform tasks
+ int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+ mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+ mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+ mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ startT.setColor(mTaskSurface, mTmpColor);
+ }
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ startT.unsetColor(mTaskSurface);
}
if (mCaptionWindowManager == null) {
@@ -305,7 +348,7 @@
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(captionWidth, captionHeight,
+ new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -325,7 +368,38 @@
}
}
- int getCaptionHeightId() {
+ /**
+ * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
+ */
+ private void updateCaptionVisibility(View rootView, int displayId) {
+ final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
+ for (int i = 0; i < insetsState.sourceSize(); i++) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() != statusBars()) {
+ continue;
+ }
+
+ mIsCaptionVisible = source.isVisible();
+ setCaptionVisibility(rootView, mIsCaptionVisible);
+
+ return;
+ }
+ }
+
+ private void setCaptionVisibility(View rootView, boolean visible) {
+ if (rootView == null) {
+ return;
+ }
+ final int v = visible ? View.VISIBLE : View.GONE;
+ final View captionView = rootView.findViewById(getCaptionViewId());
+ captionView.setVisibility(v);
+ }
+
+ int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return Resources.ID_NULL;
+ }
+
+ int getCaptionViewId() {
return Resources.ID_NULL;
}
@@ -382,6 +456,7 @@
public void close() {
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
releaseViews();
+ mTaskSurface.release();
}
static int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -398,6 +473,13 @@
return resources.getDimension(resourceId);
}
+ private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
+ Supplier<SurfaceControl> surfaceControlSupplier) {
+ final SurfaceControl copy = surfaceControlSupplier.get();
+ copy.copyFrom(sc, "WindowDecoration");
+ return copy;
+ }
+
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
@@ -408,13 +490,10 @@
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
- * @param shadowRadius radius of the shadow of the new window
- * @param cornerRadius radius of the corners of the new window
* @return the {@link AdditionalWindow} that was added.
*/
AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
- SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
- int cornerRadius) {
+ SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -425,8 +504,6 @@
t.setPosition(windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
- .setShadowRadius(windowSurfaceControl, shadowRadius)
- .setCornerRadius(windowSurfaceControl, cornerRadius)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
@@ -447,8 +524,9 @@
* Adds caption inset source to a WCT
*/
public void addCaptionInset(WindowContainerTransaction wct) {
- final int captionHeightId = getCaptionHeightId();
- if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+ final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode());
+ if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL
+ || !mIsCaptionVisible) {
return;
}
@@ -470,6 +548,8 @@
int mCaptionX;
int mCaptionY;
+ Configuration mWindowDecorConfig;
+
boolean mApplyStartTransactionOnDraw;
void reset() {
@@ -484,10 +564,12 @@
mCaptionY = 0;
mApplyStartTransactionOnDraw = false;
+ mWindowDecorConfig = null;
}
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mCaptionHeight;
int mWidth;
int mHeight;
T mRootView;
@@ -495,6 +577,7 @@
void reset() {
mWidth = 0;
mHeight = 0;
+ mCaptionHeight = 0;
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index a9eb882..589a813 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -1,13 +1,23 @@
package com.android.wm.shell.windowdecor.viewholder
+import android.annotation.ColorInt
import android.app.ActivityManager.RunningTaskInfo
import android.content.res.ColorStateList
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
+import android.content.res.Configuration
+import android.graphics.Bitmap
+import android.graphics.Color
import android.view.View
+import android.view.View.OnLongClickListener
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.content.withStyledAttributes
+import com.android.internal.R.attr.materialColorOnSecondaryContainer
+import com.android.internal.R.attr.materialColorOnSurface
+import com.android.internal.R.attr.materialColorSecondaryContainer
+import com.android.internal.R.attr.materialColorSurfaceContainerHigh
+import com.android.internal.R.attr.materialColorSurfaceContainerLow
+import com.android.internal.R.attr.materialColorSurfaceDim
import com.android.wm.shell.R
/**
@@ -19,8 +29,9 @@
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: View.OnClickListener,
+ onLongClickListener: OnLongClickListener,
appName: CharSequence,
- appIcon: Drawable
+ appIconBitmap: Bitmap
) : DesktopModeWindowDecorationViewHolder(rootView) {
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
@@ -39,60 +50,90 @@
openMenuButton.setOnTouchListener(onCaptionTouchListener)
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+ maximizeWindowButton.setOnTouchListener(onCaptionTouchListener)
+ maximizeWindowButton.onLongClickListener = onLongClickListener
closeWindowButton.setOnTouchListener(onCaptionTouchListener)
appNameTextView.text = appName
- appIconImageView.setImageDrawable(appIcon)
+ appIconImageView.setImageBitmap(appIconBitmap)
}
override fun bindData(taskInfo: RunningTaskInfo) {
-
- val captionDrawable = captionView.background as GradientDrawable
- taskInfo.taskDescription?.statusBarColor?.let {
- captionDrawable.setColor(it)
- }
-
- closeWindowButton.imageTintList = ColorStateList.valueOf(
- getCaptionCloseButtonColor(taskInfo))
- maximizeWindowButton.imageTintList = ColorStateList.valueOf(
- getCaptionMaximizeButtonColor(taskInfo))
- expandMenuButton.imageTintList = ColorStateList.valueOf(
- getCaptionExpandButtonColor(taskInfo))
- appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
+ captionView.setBackgroundColor(getCaptionBackgroundColor(taskInfo))
+ val color = getAppNameAndButtonColor(taskInfo)
+ val alpha = Color.alpha(color)
+ closeWindowButton.imageTintList = ColorStateList.valueOf(color)
+ maximizeWindowButton.imageTintList = ColorStateList.valueOf(color)
+ expandMenuButton.imageTintList = ColorStateList.valueOf(color)
+ appNameTextView.setTextColor(color)
+ appIconImageView.imageAlpha = alpha
+ maximizeWindowButton.imageAlpha = alpha
+ closeWindowButton.imageAlpha = alpha
+ expandMenuButton.imageAlpha = alpha
}
- private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
- return if (shouldUseLightCaptionColors(taskInfo)) {
- context.getColor(R.color.desktop_mode_caption_app_name_light)
- } else {
- context.getColor(R.color.desktop_mode_caption_app_name_dark)
+ override fun onHandleMenuOpened() {}
+
+ override fun onHandleMenuClosed() {}
+
+ @ColorInt
+ private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int {
+ val materialColorAttr: Int =
+ if (isDarkMode()) {
+ if (!taskInfo.isFocused) {
+ materialColorSurfaceContainerHigh
+ } else {
+ materialColorSurfaceDim
+ }
+ } else {
+ if (!taskInfo.isFocused) {
+ materialColorSurfaceContainerLow
+ } else {
+ materialColorSecondaryContainer
+ }
}
+ context.withStyledAttributes(null, intArrayOf(materialColorAttr), 0, 0) {
+ return getColor(0, 0)
+ }
+ return 0
}
- private fun getCaptionCloseButtonColor(taskInfo: RunningTaskInfo): Int {
- return if (shouldUseLightCaptionColors(taskInfo)) {
- context.getColor(R.color.desktop_mode_caption_close_button_light)
- } else {
- context.getColor(R.color.desktop_mode_caption_close_button_dark)
+ @ColorInt
+ private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo): Int {
+ val materialColorAttr = when {
+ isDarkMode() -> materialColorOnSurface
+ else -> materialColorOnSecondaryContainer
}
+ val appDetailsOpacity = when {
+ isDarkMode() && !taskInfo.isFocused -> DARK_THEME_UNFOCUSED_OPACITY
+ !isDarkMode() && !taskInfo.isFocused -> LIGHT_THEME_UNFOCUSED_OPACITY
+ else -> FOCUSED_OPACITY
+ }
+ context.withStyledAttributes(null, intArrayOf(materialColorAttr), 0, 0) {
+ val color = getColor(0, 0)
+ return if (appDetailsOpacity == FOCUSED_OPACITY) {
+ color
+ } else {
+ Color.argb(
+ appDetailsOpacity,
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)
+ )
+ }
+ }
+ return 0
}
- private fun getCaptionMaximizeButtonColor(taskInfo: RunningTaskInfo): Int {
- return if (shouldUseLightCaptionColors(taskInfo)) {
- context.getColor(R.color.desktop_mode_caption_maximize_button_light)
- } else {
- context.getColor(R.color.desktop_mode_caption_maximize_button_dark)
- }
- }
-
- private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
- return if (shouldUseLightCaptionColors(taskInfo)) {
- context.getColor(R.color.desktop_mode_caption_expand_button_light)
- } else {
- context.getColor(R.color.desktop_mode_caption_expand_button_dark)
- }
+ private fun isDarkMode(): Boolean {
+ return context.resources.configuration.uiMode and
+ Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES
}
companion object {
private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
+ private const val DARK_THEME_UNFOCUSED_OPACITY = 140 // 55%
+ private const val LIGHT_THEME_UNFOCUSED_OPACITY = 166 // 65%
+ private const val FOCUSED_OPACITY = 255
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 9374ac9..4930cb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -1,11 +1,15 @@
package com.android.wm.shell.windowdecor.viewholder
+import android.animation.ObjectAnimator
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.res.ColorStateList
-import android.graphics.drawable.GradientDrawable
+import android.graphics.Color
import android.view.View
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.widget.ImageButton
import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
@@ -17,6 +21,10 @@
onCaptionButtonClickListener: View.OnClickListener
) : DesktopModeWindowDecorationViewHolder(rootView) {
+ companion object {
+ private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
+ }
+
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
@@ -28,13 +36,19 @@
override fun bindData(taskInfo: RunningTaskInfo) {
taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
- val captionDrawable = captionView.background as GradientDrawable
- captionDrawable.setColor(captionColor)
+ captionView.setBackgroundColor(captionColor)
}
-
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
+ override fun onHandleMenuOpened() {
+ animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
+ }
+
+ override fun onHandleMenuClosed() {
+ animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
+ }
+
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
@@ -42,4 +56,30 @@
context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
}
}
+
+ /**
+ * Whether the caption items should use the 'light' color variant so that there's good contrast
+ * with the caption background color.
+ */
+ private fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+ return taskInfo.taskDescription
+ ?.let { taskDescription ->
+ if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ }
+ } ?: false
+ }
+
+ /** Animate appearance/disappearance of caption handle as the handle menu is animated. */
+ private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
+ val animator =
+ ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
+ duration = CAPTION_HANDLE_ANIMATION_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ animator.start()
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 49e8d15..690b4e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -1,11 +1,8 @@
package com.android.wm.shell.windowdecor.viewholder
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
-import android.graphics.Color
import android.view.View
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
/**
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
@@ -20,19 +17,9 @@
*/
abstract fun bindData(taskInfo: RunningTaskInfo)
- /**
- * Whether the caption items should use the 'light' color variant so that there's good contrast
- * with the caption background color.
- */
- protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.taskDescription
- ?.let { taskDescription ->
- if (Color.alpha(taskDescription.statusBarColor) != 0 &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
- } else {
- taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
- }
- } ?: false
- }
+ /** Callback when the handle menu is opened. */
+ abstract fun onHandleMenuOpened()
+
+ /** Callback when the handle menu is closed. */
+ abstract fun onHandleMenuClosed()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index b062fbd..4abaf5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -28,36 +28,6 @@
srcs: ["src/com/android/wm/shell/flicker/utils/*.kt"],
}
-filegroup {
- name: "WMShellFlickerTestsBase-src",
- srcs: ["src/com/android/wm/shell/flicker/*.kt"],
-}
-
-filegroup {
- name: "WMShellFlickerTestsBubbles-src",
- srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
-}
-
-filegroup {
- name: "WMShellFlickerTestsPip-src",
- srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
-}
-
-filegroup {
- name: "WMShellFlickerTestsSplitScreen-src",
- srcs: [
- "src/com/android/wm/shell/flicker/splitscreen/*.kt",
- "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerServiceTests-src",
- srcs: [
- "src/com/android/wm/shell/flicker/service/**/*.kt",
- ],
-}
-
java_library {
name: "wm-shell-flicker-utils",
platform_apis: true,
@@ -77,13 +47,12 @@
"wm-flicker-common-assertions",
"launcher-helper-lib",
"launcher-aosp-tapl",
+ "com_android_wm_shell_flags_lib",
],
}
java_defaults {
name: "WMShellFlickerTestsDefault",
- manifest: "manifests/AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
platform_apis: true,
certificate: "platform",
optimize: {
@@ -97,6 +66,7 @@
"flickertestapplib",
"flickerlib",
"flickerlib-helpers",
+ "flickerlib-trace_processor_shell",
"platform-test-annotations",
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
@@ -105,72 +75,11 @@
],
data: [
":FlickerTestApp",
- "trace_config/*",
],
}
-android_test {
- name: "WMShellFlickerTestsOther",
+java_library {
+ name: "WMShellFlickerTestsBase",
defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestOther.xml"],
- package_name: "com.android.wm.shell.flicker",
- instrumentation_target_package: "com.android.wm.shell.flicker",
- srcs: [
- "src/**/*.java",
- "src/**/*.kt",
- ],
- exclude_srcs: [
- ":WMShellFlickerTestsBubbles-src",
- ":WMShellFlickerTestsPip-src",
- ":WMShellFlickerTestsSplitScreen-src",
- ":WMShellFlickerServiceTests-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsBubbles",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
- package_name: "com.android.wm.shell.flicker.bubbles",
- instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsBubbles-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsPip",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestPip.xml"],
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsPip-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsSplitScreen",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
- package_name: "com.android.wm.shell.flicker.splitscreen",
- instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsSplitScreen-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerServiceTests",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestService.xml"],
- package_name: "com.android.wm.shell.flicker.service",
- instrumentation_target_package: "com.android.wm.shell.flicker.service",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerServiceTests-src",
- ],
+ srcs: ["src/com/android/wm/shell/flicker/*.kt"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
new file mode 100644
index 0000000..e151ab2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2020 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsAppCompat-src",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsOther",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker",
+ instrumentation_target_package: "com.android.wm.shell.flicker",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [":WMShellFlickerTestsAppCompat-src"],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidManifest.xml
similarity index 67%
rename from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
rename to libs/WindowManager/Shell/tests/flicker/appcompat/AndroidManifest.xml
index 6a87de4..2af1e74 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidManifest.xml
@@ -1,18 +1,18 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
@@ -45,9 +45,13 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<!-- Enable bubble notification-->
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
- <!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
@@ -65,4 +69,9 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
similarity index 91%
copy from libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index c8a9637..5b2ffec 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -57,6 +57,14 @@
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -87,14 +95,6 @@
<option name="pull-pattern-keys" value="perfetto_file_path"/>
<option name="directory-keys"
value="/data/user/0/com.android.wm.shell.flicker/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/res/xml/network_security_config.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
copy to libs/WindowManager/Shell/tests/flicker/appcompat/res/xml/network_security_config.xml
index ef30060..4bd9ca0 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/res/xml/network_security_config.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_title_color"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
-</shape>
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 36acb58..adf92d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -28,7 +28,6 @@
import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.utils.appWindowKeepVisible
import com.android.wm.shell.flicker.utils.layerKeepVisible
-
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -37,9 +36,7 @@
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
- @JvmField
- @Rule
- val letterboxRule: LetterboxRule = LetterboxRule()
+ @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule()
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -53,7 +50,7 @@
}
@Before
- fun before() {
+ fun setUp() {
Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
similarity index 70%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
index 5a1136f..181474f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -23,16 +23,14 @@
import org.junit.runner.Description
import org.junit.runners.model.Statement
-/**
- * JUnit Rule to handle letterboxStyles and states
- */
+/** JUnit Rule to handle letterboxStyles and states */
class LetterboxRule(
- private val withLetterboxEducationEnabled: Boolean = false,
- private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
- private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+ private val withLetterboxEducationEnabled: Boolean = false,
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
) : TestRule {
- private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+ private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) }
private lateinit var _letterboxStyle: MutableMap<String, String>
val letterboxStyle: Map<String, String>
@@ -59,26 +57,18 @@
resetLetterboxStyle()
_letterboxStyle = mapLetterboxStyle()
val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
- var hasLetterboxEducationStateChanged = false
if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
- hasLetterboxEducationStateChanged = true
- execAdb("wm set-letterbox-style --isEducationEnabled " +
- withLetterboxEducationEnabled)
+ execAdb("wm set-letterbox-style --isEducationEnabled " + withLetterboxEducationEnabled)
}
- return try {
- object : Statement() {
- @Throws(Throwable::class)
- override fun evaluate() {
+ return object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ try {
base!!.evaluate()
+ } finally {
+ resetLetterboxStyle()
}
}
- } finally {
- if (hasLetterboxEducationStateChanged) {
- execAdb("wm set-letterbox-style --isEducationEnabled " +
- isLetterboxEducationEnabled
- )
- }
- resetLetterboxStyle()
}
}
@@ -100,9 +90,10 @@
execAdb("wm reset-letterbox-style")
}
- private fun asInt(str: String?): Int? = try {
- str?.toInt()
- } catch (e: NumberFormatException) {
- null
- }
-}
\ No newline at end of file
+ private fun asInt(str: String?): Int? =
+ try {
+ str?.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index 67d5718..1e5e42f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -66,7 +66,8 @@
*/
@Postsubmit
@Test
- fun letterboxAppFocusedAtEnd() = flicker.assertEventLog { focusChanges(letterboxApp.`package`) }
+ fun letterboxAppFocusedAtEnd() =
+ flicker.assertEventLog { focusChanges(letterboxApp.packageName) }
@Postsubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index e6ca261..2fa1ec3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -54,9 +54,7 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
- setup {
- letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
- }
+ setup { letterboxTranslucentLauncherApp.launchViaIntent(wmHelper) }
transitions {
waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
}
@@ -66,9 +64,7 @@
}
}
- /**
- * Checks the transparent activity is launched on top of the opaque one
- */
+ /** Checks the transparent activity is launched on top of the opaque one */
@Postsubmit
@Test
fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
@@ -79,18 +75,14 @@
}
}
- /**
- * Checks that the activity is letterboxed
- */
+ /** Checks that the activity is letterboxed */
@Postsubmit
@Test
fun translucentActivityIsLetterboxed() {
flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
}
- /**
- * Checks that the translucent activity inherits bounds from the opaque one.
- */
+ /** Checks that the translucent activity inherits bounds from the opaque one. */
@Postsubmit
@Test
fun translucentActivityInheritsBoundsFromOpaqueActivity() {
@@ -100,29 +92,26 @@
}
}
- /**
- * Checks that the translucent activity has rounded corners
- */
+ /** Checks that the translucent activity has rounded corners */
@Postsubmit
@Test
fun translucentActivityHasRoundedCorners() {
- flicker.assertLayersEnd {
- this.hasRoundedCorners(letterboxTranslucentApp)
- }
+ flicker.assertLayersEnd { this.hasRoundedCorners(letterboxTranslucentApp) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
- * navigation modes.
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
+ * modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory
- .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_90)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index d3f3c5b..b74aa1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -49,8 +49,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
- BaseAppCompat(flicker) {
+class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
new file mode 100644
index 0000000..446aad8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 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.flicker.appcompat
+
+import android.os.Build
+import android.platform.test.annotations.Postsubmit
+import android.system.helpers.CommandsHelper
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test rotating an immersive app in fullscreen.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:RotateImmersiveAppInFullscreenTest`
+ *
+ * Actions:
+ * ```
+ * Rotate the device by 90 degrees to trigger a rotation through sensors
+ * Verify that the button exists
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions that are inherited from
+ * the `BaseTest` are ignored due to the nature of the immersive apps.
+ *
+ * This test only works with Cuttlefish devices.
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RotateImmersiveAppInFullscreenTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
+
+ private val immersiveApp =
+ LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.PortraitImmersiveActivity.LABEL,
+ component = ActivityOptions.PortraitImmersiveActivity.COMPONENT.toFlickerComponent()
+ )
+
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+ private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) }
+
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+
+ private val isCuttlefishDevice: Boolean = Build.MODEL.contains("Cuttlefish")
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ immersiveApp.launchViaIntent(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Display not found")
+ }
+ transitions {
+ if (isCuttlefishDevice) {
+ // Simulates a device rotation through sensors because the rotation button
+ // only appears in a rotation event through sensors
+ execAdb("/vendor/bin/cuttlefish_sensor_injection rotate 0")
+ // verify rotation button existence
+ val rotationButtonSelector = By.res(LAUNCHER_PACKAGE, "rotate_suggestion")
+ uiDevice.wait(Until.hasObject(rotationButtonSelector), FIND_TIMEOUT)
+ uiDevice.findObject(rotationButtonSelector)
+ ?: error("rotation button not found")
+ }
+ }
+ teardown { immersiveApp.exit(wmHelper) }
+ }
+
+ @Before
+ fun setUpForImmersiveAppTests() {
+ Assume.assumeTrue(isCuttlefishDevice)
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun taskBarWindowIsAlwaysVisible() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun navBarWindowIsAlwaysVisible() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarWindowIsAlwaysVisible() {}
+
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarLayerPositionAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {}
+
+ /** Test that app is fullscreen by checking status bar and task bar visibility. */
+ @Postsubmit
+ @Test
+ fun appWindowFullScreen() {
+ flicker.assertWmEnd {
+ this.isAppWindowInvisible(ComponentNameMatcher.STATUS_BAR)
+ .isAppWindowInvisible(ComponentNameMatcher.TASK_BAR)
+ .visibleRegion(immersiveApp)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /** Test that app is in the original rotation we have set up. */
+ @Postsubmit
+ @Test
+ fun appInOriginalRotation() {
+ flicker.assertWmEnd { this.hasRotation(Rotation.ROTATION_90) }
+ }
+
+ companion object {
+ private var startDisplayBounds = Rect.EMPTY
+ const val LAUNCHER_PACKAGE = "com.google.android.apps.nexuslauncher"
+
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_90),
+ // TODO(b/292403378): 3 button mode not added as rotation button is hidden in
+ // taskbar
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
similarity index 70%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
rename to libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
index ea0392c..9792c85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -33,20 +33,20 @@
abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
- protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
- instrumentation,
- launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
- component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
- )
- protected val letterboxTranslucentApp = LetterboxAppHelper(
- instrumentation,
- launcherName = ActivityOptions.TransparentActivity.LABEL,
- component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
- )
+ protected val letterboxTranslucentLauncherApp =
+ LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+ component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+ )
+ protected val letterboxTranslucentApp =
+ LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.TransparentActivity.LABEL,
+ component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+ )
- @JvmField
- @Rule
- val letterboxRule: LetterboxRule = LetterboxRule()
+ @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule()
@Before
fun before() {
@@ -54,10 +54,7 @@
}
protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
- device.wait(
- Until.findObject(By.text("Launch Transparent")),
- FIND_TIMEOUT
- )
+ device.wait(Until.findObject(By.text("Launch Transparent")), FIND_TIMEOUT)
protected fun FlickerTestData.goBack() = device.pressBack()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
similarity index 90%
copy from libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
copy to libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
index 406ada9..5a017ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
atrace_categories: "sched_process_exit"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker"
atrace_apps: "com.android.wm.shell.flicker.other"
- atrace_apps: "com.android.wm.shell.flicker.bubbles"
- atrace_apps: "com.android.wm.shell.flicker.pip"
- atrace_apps: "com.android.wm.shell.flicker.splitscreen"
atrace_apps: "com.google.android.apps.nexuslauncher"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
new file mode 100644
index 0000000..f0b4f1f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2020 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsBubbles",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.bubbles",
+ instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml
similarity index 66%
copy from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml
index 6a87de4..e6e6f1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml
@@ -1,22 +1,22 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.wm.shell.flicker">
+ package="com.android.wm.shell.flicker.bubble">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
<!-- Read and write traces from external storage -->
@@ -45,9 +45,13 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<!-- Enable bubble notification-->
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
- <!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
@@ -65,4 +69,9 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.bubble"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
similarity index 91%
copy from libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index c8a9637..9f7d9fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -57,6 +57,14 @@
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -86,15 +94,7 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
- <option name="directory-keys"
value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS b/libs/WindowManager/Shell/tests/flicker/bubble/OWNERS
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS
rename to libs/WindowManager/Shell/tests/flicker/bubble/OWNERS
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/tests/flicker/bubble/res/xml/network_security_config.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
copy to libs/WindowManager/Shell/tests/flicker/bubble/res/xml/network_security_config.xml
index ef30060..4bd9ca0 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/res/xml/network_security_config.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_title_color"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
-</shape>
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
similarity index 85%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 5c7d1d8..0c36e29 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -31,6 +31,7 @@
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
+import com.android.server.wm.flicker.helpers.MultiWindowUtils
import com.android.wm.shell.flicker.BaseTest
import org.junit.runners.Parameterized
@@ -47,7 +48,7 @@
private val uid =
context.packageManager
- .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0))
+ .getApplicationInfo(testApp.packageName, PackageManager.ApplicationInfoFlags.of(0))
.uid
@JvmOverloads
@@ -56,8 +57,12 @@
): FlickerBuilder.() -> Unit {
return {
setup {
+ MultiWindowUtils.executeShellCommand(
+ instrumentation,
+ "settings put secure force_hide_bubbles_user_education 1"
+ )
notifyManager.setBubblesAllowed(
- testApp.`package`,
+ testApp.packageName,
uid,
NotificationManager.BUBBLE_PREFERENCE_ALL
)
@@ -67,8 +72,12 @@
}
teardown {
+ MultiWindowUtils.executeShellCommand(
+ instrumentation,
+ "settings put secure force_hide_bubbles_user_education 0"
+ )
notifyManager.setBubblesAllowed(
- testApp.`package`,
+ testApp.packageName,
uid,
NotificationManager.BUBBLE_PREFERENCE_NONE
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
rename to libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index bc565bc..55039f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,12 +17,11 @@
package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import androidx.test.filters.FlakyTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
@@ -40,12 +39,10 @@
* Switch in different bubble notifications
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-open class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) :
- BaseBubbleScreen(flicker) {
+class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
similarity index 91%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 3f28ae8..9ca7bf1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -20,12 +20,12 @@
import android.graphics.Point
import android.platform.test.annotations.Presubmit
import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
@@ -42,10 +42,9 @@
* Dismiss a bubble notification
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
+class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
@@ -80,7 +79,8 @@
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
- LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(testApp)
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+ listOf(testApp, ComponentNameMatcher(className = "Bubbles!#"))
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 26aca18..b007e6b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.bubble
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -24,6 +23,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.view.WindowInsets
import android.view.WindowManager
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
rename to libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 5085394..4959672 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -20,7 +20,6 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
@@ -39,10 +38,9 @@
* The activity for the bubble is launched
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
rename to libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index a926bb7..0d95574 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -20,7 +20,6 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
@@ -38,10 +37,9 @@
* Send a bubble notification
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
+class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
similarity index 90%
copy from libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
copy to libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
index 406ada9..1599831 100644
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
atrace_categories: "sched_process_exit"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker"
- atrace_apps: "com.android.wm.shell.flicker.other"
atrace_apps: "com.android.wm.shell.flicker.bubbles"
- atrace_apps: "com.android.wm.shell.flicker.pip"
- atrace_apps: "com.android.wm.shell.flicker.splitscreen"
atrace_apps: "com.google.android.apps.nexuslauncher"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
deleted file mode 100644
index 437871f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.bubble">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.bubble"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
deleted file mode 100644
index cf642f6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
deleted file mode 100644
index 5a8155a..0000000
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.pip">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.pip"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
deleted file mode 100644
index c7aca1a..0000000
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.service">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.service"
- android:label="WindowManager Flicker Service Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
deleted file mode 100644
index 887d8db..0000000
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.splitscreen">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.splitscreen"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
new file mode 100644
index 0000000..e61f762
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -0,0 +1,147 @@
+//
+// Copyright (C) 2020 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip1-src",
+ srcs: [
+ "src/**/A*.kt",
+ "src/**/B*.kt",
+ "src/**/C*.kt",
+ "src/**/D*.kt",
+ "src/**/S*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip2-src",
+ srcs: [
+ "src/**/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip3-src",
+ srcs: ["src/**/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPipCommon-src",
+ srcs: ["src/**/common/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPipApps-src",
+ srcs: ["src/**/apps/*.kt"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip1",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip2",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip3",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsPip3-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPipApps-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPipApps",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip.apps",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsPipApps-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPipAppsCSuite",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["AndroidManifest.xml"],
+ package_name: "com.android.wm.shell.flicker.pip.apps",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsPipApps-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ test_suites: [
+ "device-tests",
+ "csuite",
+ ],
+ data: ["trace_config/*"],
+}
+
+csuite_test {
+ name: "csuite-1p3p-pip-flickers",
+ test_plan_include: "csuitePlan.xml",
+ test_config_template: "csuiteDefaultTemplate.xml",
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml
similarity index 64%
copy from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml
index 6a87de4..6d5423b 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml
@@ -1,22 +1,22 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.wm.shell.flicker">
+ package="com.android.wm.shell.flicker.pip">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
<!-- Read and write traces from external storage -->
@@ -45,9 +45,13 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<!-- Enable bubble notification-->
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
- <!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
@@ -65,4 +69,12 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
+
+ <!-- Enable mocking GPS location -->
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.pip"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
similarity index 91%
rename from libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
rename to libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index c8a9637..882b200 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -57,6 +57,14 @@
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -86,15 +94,9 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.pip.apps/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS b/libs/WindowManager/Shell/tests/flicker/pip/OWNERS
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS
rename to libs/WindowManager/Shell/tests/flicker/pip/OWNERS
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
similarity index 72%
copy from libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index c8a9637..f5a8655 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+<configuration description="Runs WindowManager Shell Flicker Tests WMShellFlickerTestsPipAppsCSuite">
<option name="test-tag" value="FlickerTests"/>
<!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
<option name="isolated-storage" value="false"/>
@@ -45,18 +45,51 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put global package_verifier_user_consent -1"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ <option name="teardown-command"
+ value="settings put global package_verifier_user_consent 1"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="WMShellFlickerTestsPipAppsCSuite.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+
+ <!-- Needed for installing apk's from Play Store -->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="aapt-version" value="AAPT2"/>
+ <option name="throw-if-not-found" value="false"/>
+ <option name="install-arg" value="-d"/>
+ <option name="install-arg" value="-g"/>
+ <option name="install-arg" value="-r"/>
+ <option name="test-file-name" value="pstash://com.netflix.mediaclient"/>
+ </target_preparer>
+
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.shell android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.shell android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Use app crawler to log into Netflix -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="am start -n com.netflix.mediaclient/com.netflix.mediaclient.ui.login.LoginActivity"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="set-option" value="package-name:com.netflix.mediaclient"/>
+ <option name="set-option" value="ui-automator-mode:true"/>
+ <option name="class" value="com.android.csuite.tests.AppCrawlTest" />
+ </test>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -69,7 +102,7 @@
<option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="{PACKAGE}"/>
+ <option name="package" value="com.android.wm.shell.flicker.pip.apps"/>
<option name="shell-timeout" value="6600s"/>
<option name="test-timeout" value="6000s"/>
<option name="hidden-api-checks" value="false"/>
@@ -86,15 +119,7 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.pip.apps/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuitePlan.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuitePlan.xml
new file mode 100644
index 0000000..a2fc6b4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuitePlan.xml
@@ -0,0 +1,3 @@
+<configuration description="Flicker tests C-Suite Crawler Test Plan">
+ <target_preparer class="com.android.csuite.core.AppCrawlTesterHostPreparer"/>
+</configuration>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/tests/flicker/pip/res/xml/network_security_config.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
copy to libs/WindowManager/Shell/tests/flicker/pip/res/xml/network_security_config.xml
index ef30060..4bd9ca0 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/res/xml/network_security_config.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_title_color"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
-</shape>
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index c335d3d..a5c2c89 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -80,12 +80,17 @@
secondAppForSplitScreen.launchViaIntent(wmHelper)
pipApp.launchViaIntent(wmHelper)
tapl.goHome()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ pipApp,
+ secondAppForSplitScreen,
+ flicker.scenario.startRotation
+ )
pipApp.enableAutoEnterForPipActivity()
}
teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
pipApp.exit(wmHelper)
secondAppForSplitScreen.exit(wmHelper)
}
@@ -126,9 +131,18 @@
if (tapl.isTablet) {
flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
} else {
- // on phones home does not rotate in landscape, PiP enters back to portrait
- // orientation so use display bounds from that orientation for assertion
- flicker.assertWmVisibleRegion(pipApp) { coversAtMost(portraitDisplayBounds) }
+ // on phones home screen does not rotate in landscape, PiP enters back to portrait
+ // orientation - if we go from landscape to portrait it should switch between the bounds
+ // otherwise it should be the same as tablet (i.e. portrait to portrait)
+ if (flicker.scenario.isLandscapeOrSeascapeAtStart) {
+ flicker.assertWmVisibleRegion(pipApp) {
+ // first check against landscape bounds then against portrait bounds
+ coversAtMost(displayBounds).then().coversAtMost(portraitDisplayBounds)
+ }
+ } else {
+ // always check against the display bounds which do not change during transition
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 2f7a25e..af2db12 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -20,7 +20,9 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -53,8 +55,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
+open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
@@ -64,15 +65,9 @@
}
}
- override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- }
+ override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
- @Presubmit
+ @FlakyTest(bugId = 293133362)
@Test
override fun pipLayerReduces() {
flicker.assertLayers {
@@ -84,7 +79,7 @@
}
/** Checks that [pipApp] window is animated towards default position in right bottom corner */
- @Presubmit
+ @FlakyTest(bugId = 255578530)
@Test
fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
@@ -107,4 +102,10 @@
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
+
+ @FlakyTest(bugId = 289943985)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 68bc9a2..9256725 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -21,7 +21,7 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,11 +49,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions {
val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index dc48696..002c019 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -20,7 +20,7 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,11 +49,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.closePipWindow(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
similarity index 92%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 5e39262..4cc9547 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -20,7 +20,7 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -40,11 +40,10 @@
* Press Home button or swipe up to go Home and put [pipApp] in pip mode
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
@@ -54,13 +53,7 @@
}
}
- override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- }
+ override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ec35837..8207b85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
@@ -28,13 +27,14 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
+import com.android.wm.shell.flicker.pip.common.PipTransition
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -65,11 +65,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = FixedOrientationAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 76c811c..cc94367 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -19,7 +19,7 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -46,7 +46,6 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index b80b748..7da4429 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -19,7 +19,7 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -48,11 +48,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
ExitPipToAppTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index f003ed8..0ad9e4c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -19,7 +19,7 @@
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -47,11 +47,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
+class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index a1d3a11..89a6c93 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -23,7 +23,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,11 +51,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.doubleClickPipWindow(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 8c8d280..8978af0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -22,7 +22,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,11 +30,10 @@
import org.junit.runners.Parameterized
/** Test expanding a pip window via pinch out gesture. */
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 421ad75..4776206 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -21,6 +21,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index dffc822..425cbfaff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -25,9 +25,9 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,11 +35,10 @@
import org.junit.runners.Parameterized
/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
override val thisTransition: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 992f1bc..18f30d9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -21,6 +21,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
similarity index 73%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
copy to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 0295741..36047cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -22,33 +22,27 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/** Test minimizing a pip window via pinch in gesture. */
-@RequiresDevice
+/** Test changing aspect ratio of pip. */
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
- transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
+ transitions { pipApp.changeAspectRatio() }
}
- /** Checks that the visible region area of [pipApp] always decreases during the animation. */
@Presubmit
@Test
- fun pipLayerAreaDecreases() {
- flicker.assertLayers {
- val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
- pipLayerList.zipWithNext { previous, current ->
- current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
- }
- }
+ fun pipAspectRatioChangesProperly() {
+ flicker.assertLayersStart { this.visibleRegion(pipApp).isSameAspectRatio(16, 9) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).isSameAspectRatio(1, 2) }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 0c6fc56..c7f2786 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -23,6 +23,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
similarity index 62%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index de64f78..cabc1cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,16 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.graphics.Rect
-import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,6 +35,7 @@
import org.junit.runners.Parameterized
/** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */
+@FlakyTest(bugId = 294993100)
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -80,8 +82,8 @@
/**
* Checks that the visible region area of [pipApp] moves to closest edge during the animation.
*/
- @Presubmit
@Test
+ @FlakyTest(bugId = 294993100)
fun pipLayerMovesToClosestEdge() {
flicker.assertLayers {
val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
@@ -95,6 +97,90 @@
}
}
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun hasAtMostOnePipDismissOverlayWindow() {
+ super.hasAtMostOnePipDismissOverlayWindow()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun navBarLayerPositionAtStartAndEnd() {
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
similarity index 75%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 0295741..381f947 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -18,11 +18,13 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.subject.exceptions.IncorrectRegionException
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,14 +41,26 @@
transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
}
- /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+ /**
+ * Checks that the visible region area of [pipApp] decreases and then increases during the
+ * animation.
+ */
@Presubmit
@Test
- fun pipLayerAreaDecreases() {
+ fun pipLayerAreaDecreasesThenIncreases() {
+ val isAreaDecreasing = arrayOf(true)
flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
- current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ if (isAreaDecreasing[0]) {
+ try {
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ } catch (e: IncorrectRegionException) {
+ isAreaDecreasing[0] = false
+ }
+ } else {
+ previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c315e74..1f69847 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
@@ -27,10 +26,12 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.common.PipTransition
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 0ff9cff..308ece4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -23,9 +23,9 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,11 +55,10 @@
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
new file mode 100644
index 0000000..182a908
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2023 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.flicker.pip.apps
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.junit.FlickerBuilderProvider
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ protected abstract val standardAppHelper: StandardAppHelper
+
+ protected abstract val permissions: Array<String>
+
+ @FlickerBuilderProvider
+ override fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ instrumentation.uiAutomation.adoptShellPermissionIdentity()
+ for (permission in permissions) {
+ instrumentation.uiAutomation.grantRuntimePermission(
+ standardAppHelper.packageName,
+ permission
+ )
+ }
+ setup { flicker.scenario.setIsTablet(tapl.isTablet) }
+ transition()
+ }
+ }
+
+ /** Checks [standardAppHelper] window remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun pipAppWindowAlwaysVisible() {
+ flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
+ }
+
+ /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
+ }
+
+ /** Checks the content overlay appears then disappears during the animation */
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ super.pipOverlayLayerAppearThenDisappear()
+ }
+
+ /**
+ * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Postsubmit
+ @Test
+ override fun pipWindowRemainInsideVisibleBounds() {
+ flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ coversAtMost(displayBounds)
+ }
+ }
+
+ /**
+ * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the
+ * whole animation
+ */
+ @Postsubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ flicker.assertLayersVisibleRegion(
+ standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ ) {
+ coversAtMost(displayBounds)
+ }
+ }
+
+ /** Checks that the visible region of [standardAppHelper] always reduces during the animation */
+ @Postsubmit
+ @Test
+ override fun pipLayerReduces() {
+ flicker.assertLayers {
+ val pipLayerList =
+ this.layers { standardAppHelper.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ /** Checks that [standardAppHelper] window becomes pinned */
+ @Postsubmit
+ @Test
+ override fun pipWindowBecomesPinned() {
+ flicker.assertWm {
+ invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
+ .then()
+ .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
+ }
+ }
+
+ /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun launcherLayerBecomesVisible() {
+ super.launcherLayerBecomesVisible()
+ }
+
+ /**
+ * Checks that the focus changes between the [standardAppHelper] window and the launcher when
+ * closing the pip window
+ */
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ flicker.assertEventLog {
+ this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity")
+ }
+ }
+
+ @Postsubmit
+ @Test
+ override fun hasAtMostOnePipDismissOverlayWindow() = super.hasAtMostOnePipDismissOverlayWindow()
+
+ // ICommonAssertions.kt overrides due to Morris overlay
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ // this fails due to Morris overlay
+ }
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ // this fails due to Morris overlay
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+ *
+ * Note: Phones only
+ */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ // this fails due to Morris overlay
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+ */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+ *
+ * Note: Large screen only
+ */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ /** Checks that all parts of the screen are covered during the transition */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
new file mode 100644
index 0000000..e272958
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.flicker.pip.apps
+
+import android.Manifest
+import android.content.Context
+import android.location.Criteria
+import android.location.Location
+import android.location.LocationManager
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.platform.test.annotations.Postsubmit
+import android.tools.device.apphelpers.MapsAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.RequiresDevice
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from Maps app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:MapsEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch Maps and start navigation mode
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
+
+ override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS,
+ Manifest.permission.ACCESS_FINE_LOCATION)
+
+ val locationManager: LocationManager =
+ instrumentation.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ val mainHandler = Handler(Looper.getMainLooper())
+ var mockLocationEnabled = false
+
+ val updateLocation =
+ object : Runnable {
+ override fun run() {
+ // early bail out if mocking location is not enabled
+ if (!mockLocationEnabled) return
+ val location = Location("Googleplex")
+ location.latitude = 37.42243438411294
+ location.longitude = -122.08426281892311
+ location.time = System.currentTimeMillis()
+ location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
+ location.accuracy = 100f
+ locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location)
+ mainHandler.postDelayed(this, 5)
+ }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ locationManager.addTestProvider(
+ LocationManager.GPS_PROVIDER,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_FINE
+ )
+ locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true)
+ mockLocationEnabled = true
+ // postpone first location update to make sure GPS is set as test provider
+ mainHandler.postDelayed(updateLocation, 200)
+
+ // normal app open through the Launcher All Apps
+ // var mapsAddressOption = "Golden Gate Bridge"
+ // standardAppHelper.open()
+ // standardAppHelper.doSearch(mapsAddressOption)
+ // standardAppHelper.getDirections()
+ // standardAppHelper.startNavigation();
+
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION)
+ )
+
+ standardAppHelper.waitForNavigationToStart()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ standardAppHelper.exit(wmHelper)
+ mainHandler.removeCallbacks(updateLocation)
+ // the main looper callback might have tried to provide a new location after the
+ // provider is no longer in test mode, causing a crash, this prevents it from happening
+ mockLocationEnabled = false
+ locationManager.removeTestProvider(LocationManager.GPS_PROVIDER)
+ }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
new file mode 100644
index 0000000..32f1259
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.flicker.pip.apps
+
+import android.Manifest
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.NetflixAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from Netflix app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch Netflix and start playing a video
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+
+ override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
+ ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
+ )
+ standardAppHelper.waitForVideoPlaying()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown { standardAppHelper.exit(wmHelper) }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { tapl.goHomeFromImmersiveFullscreenApp() }
+ }
+
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // Netflix uses source rect hint, so PiP overlay is never present
+ }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.statusBarLayerPositionAtEnd()
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0),
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
new file mode 100644
index 0000000..509b32c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.flicker.pip.apps
+
+import android.Manifest
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.YouTubeAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.RequiresDevice
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from YouTube app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch YouTube and start playing a video
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+
+ override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
+ )
+ standardAppHelper.waitForVideoPlaying()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown { standardAppHelper.exit(wmHelper) }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
+
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // YouTube uses source rect hint, so PiP overlay is never present
+ }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index a17144b..751f2bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 6d20740..9c129e4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
@@ -91,7 +91,7 @@
/** Checks that [pipApp] window becomes pinned */
@Presubmit
@Test
- fun pipWindowBecomesPinned() {
+ open fun pipWindowBecomesPinned() {
flicker.assertWm {
invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) }
.then()
@@ -102,7 +102,7 @@
/** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */
@Presubmit
@Test
- fun launcherLayerBecomesVisible() {
+ open fun launcherLayerBecomesVisible() {
flicker.assertLayers {
isInvisible(ComponentNameMatcher.LAUNCHER)
.then()
@@ -117,7 +117,7 @@
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index dfffba8..9450bdd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
index a8fb63d..7e42bc1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 096af39..7b7f1d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.app.Instrumentation
import android.content.Intent
@@ -92,7 +92,7 @@
@Presubmit
@Test
- fun hasAtMostOnePipDismissOverlayWindow() {
+ open fun hasAtMostOnePipDismissOverlayWindow() {
val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
flicker.assertWm {
val overlaysPerState =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
index 000ae8f..c6cbcd0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -26,7 +26,7 @@
/** Helper class for PIP app on AndroidTV */
open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
- private val appSelector = By.pkg(`package`).depth(0)
+ private val appSelector = By.pkg(packageName).depth(0)
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
@@ -46,7 +46,7 @@
}
override fun clickObject(resId: String) {
- val selector = By.res(`package`, resId)
+ val selector = By.res(packageName, resId)
focusOnObject(selector) || error("Could not focus on `$resId` object")
uiDevice.pressDPadCenter()
}
@@ -68,7 +68,7 @@
}
fun waitUntilClosed(): Boolean {
- val appSelector = By.pkg(`package`).depth(0)
+ val appSelector = By.pkg(packageName).depth(0)
return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
rename to libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
similarity index 90%
copy from libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
copy to libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
index 406ada9..fc15ff9 100644
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
atrace_categories: "sched_process_exit"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker"
- atrace_apps: "com.android.wm.shell.flicker.other"
- atrace_apps: "com.android.wm.shell.flicker.bubbles"
atrace_apps: "com.android.wm.shell.flicker.pip"
- atrace_apps: "com.android.wm.shell.flicker.splitscreen"
atrace_apps: "com.google.android.apps.nexuslauncher"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/Android.bp b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
new file mode 100644
index 0000000..4f1a68a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2020 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerServicePlatinumTests-src",
+ srcs: [
+ "src/**/platinum/*.kt",
+ "src/**/scenarios/*.kt",
+ "src/**/common/*.kt",
+ ],
+}
+
+java_library {
+ name: "wm-shell-flicker-platinum-tests",
+ platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
+ srcs: [
+ ":WMShellFlickerServicePlatinumTests-src",
+ ],
+ static_libs: [
+ "wm-shell-flicker-utils",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerServiceTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerServicePlatinumTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [":WMShellFlickerServicePlatinumTests-src"],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
similarity index 65%
copy from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
index 6a87de4..d54b694 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
@@ -1,22 +1,22 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.wm.shell.flicker">
+ package="com.android.wm.shell.flicker.service">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
<!-- Read and write traces from external storage -->
@@ -45,9 +45,13 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<!-- Enable bubble notification-->
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
- <!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
@@ -65,4 +69,9 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.service"
+ android:label="WindowManager Flicker Service Tests">
+ </instrumentation>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
similarity index 91%
copy from libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
index c8a9637..51a55e35 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
@@ -57,6 +57,14 @@
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -86,15 +94,7 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
copy to libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
index ef30060..4bd9ca0 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_title_color"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
-</shape>
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
index 610cede..4bd7954 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service
+package com.android.wm.shell.flicker.service.common
import android.app.Instrumentation
import android.platform.test.rule.NavigationModeRule
@@ -23,6 +23,7 @@
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ArtifactSaverRule
import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.flicker.rules.LaunchAppRule
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
@@ -33,10 +34,9 @@
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
- return RuleChain.outerRule(UnlockScreenRule())
- .around(
- NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
- )
+ return RuleChain.outerRule(ArtifactSaverRule())
+ .around(UnlockScreenRule())
+ .around(NavigationModeRule(navigationMode.value, false))
.around(
LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
)
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
new file mode 100644
index 0000000..3ab6a1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 8cb25fe..69499b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -31,7 +31,8 @@
class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ // TODO(b/300260196): Not detecting SPLIT_SCREEN_EXIT right now
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
similarity index 93%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
index fa1be63..bd627f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -31,7 +31,8 @@
class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ // TODO(b/300260196): Not detecting SPLIT_SCREEN_EXIT right now
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index aa35237..a8f4d0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -31,7 +31,7 @@
class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) // SPLIT_SCREEN_EXIT not yet tagged here (b/301222449)
@Test
override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index e195360..cee9bbf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -31,7 +31,7 @@
class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) // SPLIT_SCREEN_EXIT not yet tagged here (b/301222449)
@Test
override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 5f771c7..169b5cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -31,7 +31,7 @@
class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @ExpectedScenarios(["ENTIRE_TRACE"]) // missing SPLIT_SCREEN_ENTER tag (b/301093332)
@Test
override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 729a401..412c011 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -31,7 +31,7 @@
class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @ExpectedScenarios(["ENTIRE_TRACE"]) // missing SPLIT_SCREEN_ENTER tag (b/301093332)
@Test
override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 00f6073..4ff0b4362 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -31,7 +31,7 @@
class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
- @ExpectedScenarios(["QUICKSWITCH"])
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
@Test
override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index b3340e7..930f31d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -31,7 +31,7 @@
class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
- @ExpectedScenarios(["QUICKSWITCH"])
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
@Test
override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
similarity index 82%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
copy to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index b3340e7..c744103 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -16,24 +16,22 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
import android.tools.common.flicker.FlickerConfig
import android.tools.common.flicker.annotation.ExpectedScenarios
import android.tools.common.flicker.annotation.FlickerConfigProvider
import android.tools.common.flicker.config.FlickerConfig
import android.tools.common.flicker.config.FlickerServiceConfig
import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromRecentGesturalNavPortrait :
- SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
- @ExpectedScenarios(["QUICKSWITCH"])
+ @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"])
@Test
- override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
companion object {
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
similarity index 82%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
copy to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index fa1be63..11a4e02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -16,24 +16,22 @@
package com.android.wm.shell.flicker.service.splitscreen.flicker
-import android.tools.common.Rotation
import android.tools.common.flicker.FlickerConfig
import android.tools.common.flicker.annotation.ExpectedScenarios
import android.tools.common.flicker.annotation.FlickerConfigProvider
import android.tools.common.flicker.config.FlickerConfig
import android.tools.common.flicker.config.FlickerServiceConfig
import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByDividerGesturalNavPortrait :
- DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"])
@Test
- override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
companion object {
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
similarity index 81%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index 92b6227..e37d806 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
similarity index 81%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index 92b6227..2a50912 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index e6d56b5..d5da1a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+open class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 6752c58..7fdcb9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+open class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 7c9ab99..308e954 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+open class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 4b79571..39e75bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+open class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
similarity index 80%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index 0495079..e18da17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
similarity index 80%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index 0495079..00d60e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index c78729c..d7efbc8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 30bce2f6..4eece3f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index b33ea7c..d96b056 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 07a86a5..809b690 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 9a1d127..bbdf2d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 266e268..5c29fd8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 83fc30b..a7398eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index b2f1929..eae88ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index dae92dd..7e8ee04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenFromOverviewGesturalNavLandscape :
EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 732047b..9295c33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+open class EnterSplitScreenFromOverviewGesturalNavPortrait :
EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 1de7efd..4b59e9f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 1a046aa..5ff36d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 6e88f0e..c0cb721 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index d26a29c..8c14088 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 4a552b0..7b6614b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+open class SwitchBackToSplitFromHomeGesturalNavLandscape :
SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index b7376ea..5df5be9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+open class SwitchBackToSplitFromHomeGesturalNavPortrait :
SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index b2d05e4..9d63003 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+open class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 6de31b1..9fa04b2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+open class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index aab18a6..9386aa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+open class SwitchBetweenSplitPairsGesturalNavLandscape :
SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index b074f2c..5ef2167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+open class SwitchBetweenSplitPairsGesturalNavPortrait :
SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
similarity index 82%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index c402aa4..9caab9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index c402aa4..bf484e5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index e530f63..824e454 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -51,7 +53,7 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp, rotation)
}
@Test
@@ -64,4 +66,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index e9fc437..4c39104 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index 416692c..f6d1afc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 494a246..db5a32a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 369bdfc..03170a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -19,13 +19,15 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -46,19 +48,26 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
+ tapl.goHome()
+
+ primaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- tapl.goHome()
- primaryApp.launchViaIntent(wmHelper)
+ tapl.enableBlockTimeout(true)
}
@Test
open fun enterSplitScreenByDragFromAllApps() {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
@@ -66,5 +75,6 @@
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ tapl.enableBlockTimeout(false)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
similarity index 85%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 776c397..c52ada3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -19,14 +19,18 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -47,14 +51,18 @@
@Before
fun setup() {
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
+ Assume.assumeTrue(tapl.isTablet)
// Send a notification
sendNotificationApp.launchViaIntent(wmHelper)
sendNotificationApp.postNotification(wmHelper)
tapl.goHome()
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
primaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
}
@Test
@@ -69,4 +77,8 @@
secondaryApp.exit(wmHelper)
sendNotificationApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
similarity index 83%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 5d67dc7..479d01d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -19,15 +19,18 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -49,21 +52,25 @@
fun setup() {
Assume.assumeTrue(tapl.isTablet)
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
-
tapl.goHome()
SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.enableBlockTimeout(true)
}
@Test
open fun enterSplitScreenByDragFromShortcut() {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
.getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
// TODO: Do we want this check in here? Add to the other tests?
@@ -79,5 +86,10 @@
fun teardwon() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ tapl.enableBlockTimeout(false)
+ }
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
similarity index 83%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 5bbb42f..625c56b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,14 +19,17 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -46,9 +49,13 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ tapl.enableBlockTimeout(true)
+
tapl.goHome()
SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
@@ -56,9 +63,10 @@
@Test
open fun enterSplitScreenByDragFromTaskbar() {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
@@ -66,5 +74,10 @@
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ tapl.enableBlockTimeout(false)
+ }
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index c2100f6..f1a011c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -46,9 +48,6 @@
@Before
fun setup() {
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
-
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
@@ -57,11 +56,14 @@
.withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
}
@Test
open fun enterSplitScreenFromOverview() {
- SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.splitFromOverview(tapl, device, rotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
@@ -70,4 +72,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 70f3bed..c9b1c91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -20,15 +20,17 @@
import android.graphics.Point
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -48,11 +50,12 @@
@Before
fun setup() {
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
tapl.workspace.switchToOverview().dismissAllTasks()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
@@ -148,4 +151,8 @@
val LARGE_SCREEN_DP_THRESHOLD = 600
return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 86f394d..72f2db3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -50,7 +52,7 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
@@ -67,4 +69,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index d7b611e..511de4f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -49,7 +51,7 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
@@ -66,4 +68,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index bf4c381..558d2bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -46,10 +48,12 @@
@Before
fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
@@ -66,4 +70,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 4a9c32f..ecd68295 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -51,8 +53,8 @@
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp, rotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
}
@@ -69,4 +71,8 @@
thirdApp.exit(wmHelper)
fourthApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 383a6b3..f50d5c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -19,14 +19,16 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -66,4 +68,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
similarity index 88%
copy from libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
copy to libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
index 406ada9..9f2e497 100644
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
@@ -63,11 +63,7 @@
atrace_categories: "sched_process_exit"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker"
- atrace_apps: "com.android.wm.shell.flicker.other"
- atrace_apps: "com.android.wm.shell.flicker.bubbles"
- atrace_apps: "com.android.wm.shell.flicker.pip"
- atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.android.wm.shell.flicker.service"
atrace_apps: "com.google.android.apps.nexuslauncher"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
new file mode 100644
index 0000000..f813b0d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -0,0 +1,80 @@
+//
+// Copyright (C) 2020 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenBase-src",
+ srcs: [
+ "src/**/benchmark/*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenGroup1-src",
+ srcs: [
+ "src/**/A*.kt",
+ "src/**/B*.kt",
+ "src/**/C*.kt",
+ "src/**/D*.kt",
+ "src/**/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenGroup2-src",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreenGroup1",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenBase-src",
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreenGroup2",
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenBase-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml
similarity index 65%
copy from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml
index 6a87de4..9ff2161 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml
@@ -1,22 +1,22 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.android.wm.shell.flicker">
+ package="com.android.wm.shell.flicker.splitscreen">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
<!-- Read and write traces from external storage -->
@@ -45,9 +45,13 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<!-- Enable bubble notification-->
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
- <!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
@@ -65,4 +69,9 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
similarity index 91%
copy from libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
copy to libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
index c8a9637..05f937a 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -57,6 +57,14 @@
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -86,15 +94,9 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS
new file mode 100644
index 0000000..3ab6a1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
copy to libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
index ef30060..4bd9ca0 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_title_color"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
-</shape>
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 3702be9..6b97169 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -24,6 +23,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 8b90630..51588569 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 50f6a38..fc6c2b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
similarity index 91%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index ca9c130..8b1689a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
@@ -57,7 +57,7 @@
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit
+ @FlakyTest(bugId = 291678271)
@Test
fun primaryAppLayerVisibilityChanges() {
flicker.assertLayers {
@@ -69,7 +69,7 @@
}
}
- @Presubmit
+ @FlakyTest(bugId = 291678271)
@Test
fun secondaryAppLayerVisibilityChanges() {
flicker.assertLayers {
@@ -87,7 +87,7 @@
@Test
fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
- @FlakyTest(bugId = 245472831)
+ @FlakyTest(bugId = 291678271)
@Test
fun primaryAppBoundsChanges() {
flicker.splitAppLayerBoundsChanges(
@@ -97,7 +97,7 @@
)
}
- @Presubmit
+ @FlakyTest(bugId = 291678271)
@Test
fun secondaryAppBoundsChanges() =
flicker.splitAppLayerBoundsChanges(
@@ -106,6 +106,12 @@
portraitPosTop = true
)
+ @FlakyTest(bugId = 291678271)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index f8d1e1f..99613f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -24,6 +23,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ff5d935..756a7fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -24,6 +23,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 7c71077..121b46a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 8371706..99deb92 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -24,6 +23,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 0bfdbb4..212a4e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 88bbc0e..284c32e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index e85dc24..9e6448f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index f7a9ed0..8e28712 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 66f9b85..fb0193b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
new file mode 100644
index 0000000..715a533
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 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.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch between two split pairs.
+ *
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairsNoPip`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBetweenSplitPairsNoPip(override val flicker: LegacyFlickerTest) :
+ SplitScreenBase(flicker) {
+
+ val thirdApp = SplitScreenUtils.getSendNotification(instrumentation)
+ val pipApp = PipAppHelper(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ tapl.goHome()
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ thirdApp,
+ pipApp,
+ flicker.scenario.startRotation
+ )
+ pipApp.enableAutoEnterForPipActivity()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, pipApp)
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ teardown {
+ pipApp.exit(wmHelper)
+ thirdApp.exit(wmHelper)
+ }
+ }
+
+ /** Checks that [pipApp] window won't enter pip */
+ @Presubmit
+ @Test
+ fun notEnterPip() {
+ flicker.assertWm { isNotPinned(pipApp) }
+ }
+
+ /** Checks the [pipApp] task did not reshow during transition. */
+ @Presubmit
+ @Test
+ fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
+ flicker.assertLayers {
+ this.isVisible(pipApp)
+ .then()
+ .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
+ .then()
+ .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isInvisible(pipApp)
+ .isVisible(secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ /** Checks the [pipApp] task become invisible after transition finish. */
+ @Presubmit @Test fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp)
+
+ /** Checks the [pipApp] task is in split screen bounds when transition start. */
+ @Presubmit
+ @Test
+ fun pipAppBoundsIsVisibleAtBegin() =
+ flicker.assertLayersStart {
+ this.splitAppLayerBoundsSnapToDivider(
+ pipApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true,
+ flicker.scenario.startRotation
+ )
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
similarity index 83%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 851391d..f3145c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -17,12 +17,16 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
@@ -57,6 +61,22 @@
}
@Test
+ @FlakyTest(bugId = 293578017)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ // TODO(b/293578017) remove once that bug is resolve
+ @Test
+ @Presubmit
+ fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() =
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+ listOf(WALLPAPER_BBQ_WRAPPER)
+ )
+ }
+
+ @Test
fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index e5c1e75..df1c9a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -22,7 +22,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,7 +39,16 @@
protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ textEditApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
SplitScreenUtils.copyContentInSplit(
instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index e4e1af9..d01eab0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -21,7 +21,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -36,7 +35,16 @@
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
if (tapl.isTablet) {
SplitScreenUtils.dragDividerToDismissSplit(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index b2dd02b..e36bd33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -21,7 +21,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -36,7 +35,16 @@
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 0788591..050d389 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -21,7 +21,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
@@ -38,7 +37,16 @@
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 884e451..5c43cbd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -22,8 +22,8 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -43,12 +43,14 @@
setup {
tapl.goHome()
primaryApp.launchViaIntent(wmHelper)
+ tapl.enableBlockTimeout(true)
}
transitions {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@@ -58,6 +60,11 @@
Assume.assumeTrue(tapl.isTablet)
}
+ @After
+ fun after() {
+ tapl.enableBlockTimeout(false)
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index e5c40b6..cd3fbab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -22,7 +22,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 0451001..15ad0c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -22,8 +22,8 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -43,18 +43,25 @@
Assume.assumeTrue(tapl.isTablet)
}
+ @After
+ fun after() {
+ tapl.enableBlockTimeout(false)
+ }
+
protected val thisTransition: FlickerBuilder.() -> Unit = {
setup {
tapl.goHome()
SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
+ tapl.enableBlockTimeout(true)
}
transitions {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
.getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index 9e0ca1b..ca8adb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -22,8 +22,8 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -45,9 +45,10 @@
primaryApp.launchViaIntent(wmHelper)
}
transitions {
+ tapl.showTaskbarIfHidden()
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@@ -55,6 +56,12 @@
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
+ tapl.enableBlockTimeout(true)
+ }
+
+ @After
+ fun after() {
+ tapl.enableBlockTimeout(false)
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 06b4fe7..5e5e7d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -21,7 +21,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -47,7 +46,7 @@
.waitForAndVerify()
}
transitions {
- SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.splitFromOverview(tapl, device, flicker.scenario.startRotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
similarity index 83%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
index 7ce995a..a0e437c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.splitscreen.benchmark
import android.content.Context
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -33,7 +33,10 @@
tapl.setEnableRotation(true)
setRotation(flicker.scenario.startRotation)
tapl.setExpectedRotation(flicker.scenario.startRotation.value)
- tapl.workspace.switchToOverview().dismissAllTasks()
+ val overview = tapl.workspace.switchToOverview()
+ if (overview.hasTasks()) {
+ overview.dismissAllTasks()
+ }
}
}
@@ -43,11 +46,4 @@
secondaryApp.exit(wmHelper)
}
}
-
- protected open val withoutTracing: FlickerBuilder.() -> Unit = {
- withoutLayerTracing()
- withoutWindowManagerTracing()
- withoutTransitionTracing()
- withoutTransactionsTracing()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 007b751..e39c3c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -25,7 +25,6 @@
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,7 +39,16 @@
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
SplitScreenUtils.doubleTapDividerToSwitch(device)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 10c8eeb..32284ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -22,7 +22,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,7 +39,14 @@
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index a6e750f..a926ec9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -22,7 +22,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -38,7 +37,14 @@
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index 7e8d5fb..d2e1d52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -22,7 +22,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -38,7 +37,14 @@
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 56edad1..9d6b251 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -21,7 +21,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,8 +39,22 @@
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ thirdApp,
+ fourthApp,
+ flicker.scenario.startRotation
+ )
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
}
transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 065d4d6..e71834d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,7 +22,6 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
rename to libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
index 406ada9..67316d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
@@ -63,10 +63,7 @@
atrace_categories: "sched_process_exit"
atrace_apps: "com.android.server.wm.flicker.testapp"
atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker"
- atrace_apps: "com.android.wm.shell.flicker.other"
- atrace_apps: "com.android.wm.shell.flicker.bubbles"
- atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.service"
atrace_apps: "com.android.wm.shell.flicker.splitscreen"
atrace_apps: "com.google.android.apps.nexuslauncher"
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
index 0f3e0f5..e9363f7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -38,7 +38,7 @@
* executions
*/
@FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
+ open fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
setup { flicker.scenario.setIsTablet(tapl.isTablet) }
transition()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 735fbfb..568650d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
deleted file mode 100644
index abc6b9f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ChangeActiveActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
- ChangeActiveActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
deleted file mode 100644
index ee55eca..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DragToDismissBubbleScreenTestCfArm(flicker: LegacyFlickerTest) :
- DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
deleted file mode 100644
index 6a46d23..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
- OpenActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
deleted file mode 100644
index a401cb4..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTestCfArm(flicker: LegacyFlickerTest) :
- SendBubbleNotificationTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
deleted file mode 100644
index 7a66889..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ClosePipBySwipingDownTestCfArm(flicker: LegacyFlickerTest) :
- ClosePipBySwipingDownTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
deleted file mode 100644
index 718b14b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTestCfArm(flicker: LegacyFlickerTest) :
- ClosePipWithDismissButtonTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
deleted file mode 100644
index 2b3e76a..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test will fail because of b/264261596 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTestCfArm(flicker: LegacyFlickerTest) :
- EnterPipOnUserLeaveHintTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
deleted file mode 100644
index 9264219..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test fails because of b/264261596 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientationCfArm(flicker: LegacyFlickerTest) :
- EnterPipToOtherOrientation(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
deleted file mode 100644
index 78e8049..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipViaAppUiButtonTestCfArm(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
deleted file mode 100644
index e25c0d6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaExpandButtonTestCfArm(flicker: LegacyFlickerTest) :
- ExitPipToAppViaExpandButtonTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
deleted file mode 100644
index be19f3c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaIntentTestCfArm(flicker: LegacyFlickerTest) :
- ExitPipToAppViaIntentTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
deleted file mode 100644
index 3095cac..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTestTestCfArm(flicker: LegacyFlickerTest) :
- ExpandPipOnDoubleClickTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
deleted file mode 100644
index 1a1ce68..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTestCfArm(flicker: LegacyFlickerTest) :
- ExpandPipOnPinchOpenTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
deleted file mode 100644
index 63292a4..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestCfArm(flicker: LegacyFlickerTest) :
- MovePipOnImeVisibilityChangeTest(flicker) {
- companion object {
- private const val TAG_IME_VISIBLE = "imeIsVisible"
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
deleted file mode 100644
index 2516471..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.pip
-
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowPipAndRotateDisplayCfArm(flicker: LegacyFlickerTest) : ShowPipAndRotateDisplay(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.rotationTests()
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
deleted file mode 100644
index 566adec..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-
-@RequiresDevice
-class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
deleted file mode 100644
index 71ef48b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-
-@RequiresDevice
-class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
deleted file mode 100644
index 840401c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-@RequiresDevice
-@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
deleted file mode 100644
index 7cbc1c3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
deleted file mode 100644
index 2eb81e0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 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.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index e5c124c..f1cb37e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -265,6 +265,7 @@
val dividerRegion =
layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
+ visibleRegion(component).isNotEmpty()
visibleRegion(component)
.coversAtMost(
if (displayBounds.width > displayBounds.height) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 3f8a1ae..3244ebc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -19,10 +19,12 @@
import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
+import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.IComponentMatcher
import android.tools.common.traces.component.IComponentNameMatcher
import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import android.tools.device.traces.parsers.toFlickerComponent
import android.view.InputDevice
@@ -101,13 +103,15 @@
tapl: LauncherInstrumentation,
device: UiDevice,
primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
+ secondaryApp: StandardAppHelper,
+ rotation: Rotation
) {
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- splitFromOverview(tapl, device)
+ splitFromOverview(tapl, device, rotation)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
@@ -121,7 +125,7 @@
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
- fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+ fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice, rotation: Rotation) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
// split to left side.
@@ -129,29 +133,40 @@
// TAPL's currentTask on tablet is sometimes not what we expected if the overview
// contains more than 3 task views. We need to use uiautomator directly to find the
// second task to split.
- tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val home = tapl.workspace.switchToOverview()
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ val isGridOnlyOverviewEnabled = tapl.isGridOnlyOverviewEnabled
+ if (isGridOnlyOverviewEnabled) {
+ home.currentTask.tapMenu().tapSplitMenuItem()
+ } else {
+ home.overviewActions.clickSplit()
+ }
val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
if (snapshots == null || snapshots.size < 1) {
error("Fail to find a overview snapshot to split.")
}
- // Find the second task in the upper right corner in split select mode by sorting
- // 'left' in descending order and 'top' in ascending order.
+ // Find the second task in the upper (or bottom for grid only Overview) right corner in
+ // split select mode by sorting 'left' in descending order and 'top' in ascending (or
+ // descending for grid only Overview) order.
snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
t2.getVisibleBounds().left - t1.getVisibleBounds().left
}
snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
- t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ if (isGridOnlyOverviewEnabled) {
+ t2.getVisibleBounds().top - t1.getVisibleBounds().top
+ } else {
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
}
snapshots[0].click()
} else {
- tapl.workspace
- .switchToOverview()
- .currentTask
- .tapMenu()
- .tapSplitMenuItem()
- .currentTask
- .open()
+ val rotationCheckEnabled = tapl.getExpectedRotationCheckEnabled()
+ tapl.setExpectedRotationCheckEnabled(false) // disable rotation check to enter overview
+ val home = tapl.workspace.switchToOverview()
+ tapl.setExpectedRotationCheckEnabled(rotationCheckEnabled) // restore rotation checks
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ home.currentTask.tapMenu().tapSplitMenuItem().currentTask.open()
}
SystemClock.sleep(TIMEOUT_MS)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index d09a90c..aadadd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -35,6 +35,7 @@
static_libs: [
"WindowManager-Shell",
"junit",
+ "flag-junit-base",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
@@ -49,6 +50,7 @@
"testables",
"platform-test-annotations",
"servicestests-utils",
+ "com_android_wm_shell_flags_lib",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 081c8ae..9c1a88e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,6 +16,10 @@
package com.android.wm.shell;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -40,7 +44,6 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.TaskInfo;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -356,7 +359,7 @@
public void testOnSizeCompatActivityChanged() {
final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.topActivityInSizeCompat = false;
+ taskInfo1.appCompatTaskInfo.topActivityInSizeCompat = false;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, null);
@@ -369,7 +372,7 @@
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.topActivityInSizeCompat = true;
+ taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
@@ -379,7 +382,7 @@
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.topActivityInSizeCompat = true;
+ taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
@@ -393,7 +396,7 @@
public void testOnEligibleForLetterboxEducationActivityChanged() {
final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.topActivityEligibleForLetterboxEducation = false;
+ taskInfo1.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = false;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, null);
@@ -408,7 +411,7 @@
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.topActivityEligibleForLetterboxEducation = true;
+ taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
@@ -418,7 +421,7 @@
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.topActivityEligibleForLetterboxEducation = true;
+ taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
@@ -432,7 +435,7 @@
public void testOnCameraCompatActivityChanged() {
final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+ taskInfo1.appCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, null);
@@ -446,7 +449,8 @@
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ taskInfo2.appCompatTaskInfo.cameraCompatControlState =
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
@@ -457,7 +461,8 @@
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+ taskInfo3.appCompatTaskInfo.cameraCompatControlState =
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo3.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo3);
verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
@@ -468,8 +473,9 @@
final RunningTaskInfo taskInfo4 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo4.displayId = taskInfo1.displayId;
- taskInfo4.topActivityInSizeCompat = true;
- taskInfo4.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+ taskInfo4.appCompatTaskInfo.topActivityInSizeCompat = true;
+ taskInfo4.appCompatTaskInfo.cameraCompatControlState =
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo4.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo4);
verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
@@ -479,7 +485,8 @@
final RunningTaskInfo taskInfo5 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo5.displayId = taskInfo1.displayId;
- taskInfo5.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+ taskInfo5.appCompatTaskInfo.cameraCompatControlState =
+ CAMERA_COMPAT_CONTROL_DISMISSED;
taskInfo5.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo5);
verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
@@ -489,7 +496,8 @@
final RunningTaskInfo taskInfo6 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo6.displayId = taskInfo1.displayId;
- taskInfo6.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+ taskInfo6.appCompatTaskInfo.cameraCompatControlState =
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo6.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo6);
verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
new file mode 100644
index 0000000..b91d6f9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+
+/**
+ * Basic test handler that immediately executes anything that is posted on it.
+ */
+public class TestHandler extends Handler {
+ public TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ dispatchMessage(msg);
+ return true;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 2d93047..2ac72af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -35,7 +35,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -98,4 +98,21 @@
// The animation should be empty when it is behind starting window.
assertEquals(0, animator.getDuration());
}
+
+ @Test
+ public void testInvalidCustomAnimation() {
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .build();
+ info.setAnimationOptions(TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
+ 0 /* backgroundColor */, false /* overrideTaskTransition */));
+ final Animator animator = mAnimRunner.createAnimator(
+ info, mStartTransaction, mFinishTransaction,
+ () -> mFinishCallback.onTransitionFinished(null /* wct */),
+ new ArrayList<>());
+
+ // An invalid custom animation is equivalent to jump-cut.
+ assertEquals(0, animator.getDuration());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 270dbc4..974d69b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -39,7 +39,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -187,6 +187,25 @@
verifyNoMoreInteractions(mFinishTransaction);
}
+ @Test
+ public void testShouldAnimate_containsAnimationOptions() {
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS, TASK_BOUNDS))
+ .build();
+
+ info.setAnimationOptions(TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
+ 0 /* backgroundColor */, false /* overrideTaskTransition */));
+ assertTrue(mController.shouldAnimate(info));
+
+ info.setAnimationOptions(TransitionInfo.AnimationOptions
+ .makeSceneTransitionAnimOptions());
+ assertFalse(mController.shouldAnimate(info));
+
+ info.setAnimationOptions(TransitionInfo.AnimationOptions.makeCrossProfileAnimOptions());
+ assertFalse(mController.shouldAnimate(info));
+ }
+
@UiThreadTest
@Test
public void testMergeAnimation() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
index 17ed396..e727491 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
@@ -424,6 +424,7 @@
eq(-5f), anyFloat(), eq(true))
}
+ @Ignore("Started flaking despite no changes, tracking in b/299636216")
@Test
fun testIsPropertyAnimating() {
PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 3d8bd38..771876f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -37,6 +37,7 @@
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteCallback;
@@ -44,9 +45,9 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContentResolver;
-import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationRunner;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -58,7 +59,6 @@
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
@@ -67,8 +67,8 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
+
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -85,12 +85,8 @@
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
+
private ShellInit mShellInit;
-
- @Rule
- public TestableContext mContext =
- new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
-
@Mock
private IActivityTaskManager mActivityTaskManager;
@@ -112,13 +108,19 @@
@Mock
private BackAnimationBackground mAnimationBackground;
+ @Mock
+ private InputManager mInputManager;
+
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
+ private ShellBackAnimationRegistry mShellBackAnimationRegistry;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(InputManager.class, mInputManager);
mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
mContentResolver = new TestableContentResolver(mContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
@@ -126,11 +128,24 @@
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager, mContext,
- mContentResolver, mAnimationBackground);
- mController.setEnableUAnimation(true);
+ mShellBackAnimationRegistry =
+ new ShellBackAnimationRegistry(
+ new CrossActivityBackAnimation(mContext, mAnimationBackground),
+ new CrossTaskBackAnimation(mContext, mAnimationBackground),
+ /* dialogCloseAnimation= */ null,
+ new CustomizeActivityAnimation(mContext, mAnimationBackground),
+ /* defaultBackToHomeAnimation= */ null);
+ mController =
+ new BackAnimationController(
+ mShellInit,
+ mShellController,
+ mShellExecutor,
+ new Handler(mTestableLooper.getLooper()),
+ mActivityTaskManager,
+ mContext,
+ mContentResolver,
+ mAnimationBackground,
+ mShellBackAnimationRegistry);
mShellInit.init();
mShellExecutor.flushAll();
}
@@ -138,12 +153,13 @@
private void createNavigationInfo(int backType,
boolean enableAnimation,
boolean isAnimationCallback) {
- BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
- .setType(backType)
- .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(mAppCallback)
- .setPrepareRemoteAnimation(enableAnimation)
- .setAnimationCallback(isAnimationCallback);
+ BackNavigationInfo.Builder builder =
+ new BackNavigationInfo.Builder()
+ .setType(backType)
+ .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(enableAnimation)
+ .setAnimationCallback(isAnimationCallback);
createNavigationInfo(builder);
}
@@ -166,8 +182,7 @@
}
private void triggerBackGesture() {
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- doMotionEvent(MotionEvent.ACTION_MOVE, 0);
+ doStartEvents(0, 0);
mController.setTriggerBack(true);
}
@@ -188,25 +203,28 @@
@Test
public void verifyNavigationFinishes() throws RemoteException {
- final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
- BackNavigationInfo.TYPE_CROSS_TASK,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- BackNavigationInfo.TYPE_DIALOG_CLOSE,
- BackNavigationInfo.TYPE_CALLBACK };
+ final int[] testTypes =
+ new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE,
+ BackNavigationInfo.TYPE_CALLBACK
+ };
- for (int type: testTypes) {
+ for (int type : testTypes) {
registerAnimation(type);
}
- for (int type: testTypes) {
- final ResultListener result = new ResultListener();
+ for (int type : testTypes) {
+ final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
.setOnBackInvokedCallback(mAppCallback)
.setPrepareRemoteAnimation(true)
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
- simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationStart();
mShellExecutor.flushAll();
releaseBackGesture();
simulateRemoteAnimationFinished();
@@ -225,12 +243,9 @@
/* enableAnimation = */ true,
/* isAnimationCallback = */ false);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+ doStartEvents(0, 100);
- // Check that back start and progress is dispatched when first move.
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
-
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -251,12 +266,10 @@
/* enableAnimation = */ true,
/* isAnimationCallback = */ true);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
-
// Check that back start and progress is dispatched when first move.
- doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000);
+ doStartEvents(0, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -275,10 +288,17 @@
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager, mContext,
- mContentResolver, mAnimationBackground);
+ mController =
+ new BackAnimationController(
+ shellInit,
+ mShellController,
+ mShellExecutor,
+ new Handler(mTestableLooper.getLooper()),
+ mActivityTaskManager,
+ mContext,
+ mContentResolver,
+ mAnimationBackground,
+ mShellBackAnimationRegistry);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -302,14 +322,14 @@
}
@Test
- public void ignoresGesture_transitionInProgress() throws RemoteException {
+ public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
/* enableAnimation = */ true,
/* isAnimationCallback = */ false);
triggerBackGesture();
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
releaseBackGesture();
// Check that back invocation is dispatched.
@@ -319,24 +339,62 @@
reset(mAnimatorCallback);
reset(mBackAnimationRunner);
- // Verify that we prevent animation from restarting if another gestures happens before
- // the previous transition is finished.
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+ // Verify that we prevent any interaction with the animator callback in case a new gesture
+ // starts while the current back animation has not ended, instead the gesture is queued
+ triggerBackGesture();
verifyNoMoreInteractions(mAnimatorCallback);
- // Finish back navigation.
+ // Finish previous back navigation.
simulateRemoteAnimationFinished();
- // Verify that more events from a rejected swipe cannot start animation.
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- doMotionEvent(MotionEvent.ACTION_UP, 0);
- verifyNoMoreInteractions(mAnimatorCallback);
+ // Verify that releasing the gesture causes back key to be injected
+ releaseBackGesture();
+ verify(mInputManager, times(2))
+ .injectInputEvent(any(KeyEvent.class), any(Integer.class));
// Verify that we start accepting gestures again once transition finishes.
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ doStartEvents(0, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
+ verify(mAnimatorCallback).onBackStarted(any());
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ }
+
+ @Test
+ public void queuedFinishedGesture_RunsAfterPreviousTransitionEnded() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
+
+ triggerBackGesture();
+ simulateRemoteAnimationStart();
+ releaseBackGesture();
+
+ // Check that back invocation is dispatched.
+ verify(mAnimatorCallback).onBackInvoked();
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+
+ reset(mAnimatorCallback);
+ reset(mBackAnimationRunner);
+
+ // Verify that we prevent any interaction with the animator callback in case a new gesture
+ // starts while the current back animation has not ended, instead the gesture is queued
+ triggerBackGesture();
+ releaseBackGesture();
+ verifyNoMoreInteractions(mAnimatorCallback);
+
+ // Finish previous back navigation.
+ simulateRemoteAnimationFinished();
+
+ // Verify that back key press is injected after previous back navigation has ended
+ verify(mInputManager, times(2))
+ .injectInputEvent(any(KeyEvent.class), any(Integer.class));
+
+ // Verify that we start accepting gestures again once transition finishes.
+ doStartEvents(0, 100);
+
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@@ -352,7 +410,7 @@
doNothing().when(mAnimatorCallback).onBackInvoked();
triggerBackGesture();
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
mShellExecutor.flushAll();
releaseBackGesture();
@@ -361,9 +419,8 @@
mShellExecutor.flushAll();
reset(mAnimatorCallback);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ doStartEvents(0, 100);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any());
}
@@ -375,11 +432,9 @@
/* enableAnimation = */ true,
/* isAnimationCallback = */ false);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- // Check that back start and progress is dispatched when first move.
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ doStartEvents(0, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -398,17 +453,19 @@
@Test
public void animationNotDefined() throws RemoteException {
- final int[] testTypes = new int[] {
- BackNavigationInfo.TYPE_RETURN_TO_HOME,
- BackNavigationInfo.TYPE_CROSS_TASK,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- BackNavigationInfo.TYPE_DIALOG_CLOSE};
+ final int[] testTypes =
+ new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE
+ };
- for (int type: testTypes) {
+ for (int type : testTypes) {
unregisterAnimation(type);
}
- for (int type: testTypes) {
+ for (int type : testTypes) {
final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
@@ -416,7 +473,7 @@
.setPrepareRemoteAnimation(true)
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
- simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationStart();
mShellExecutor.flushAll();
releaseBackGesture();
@@ -466,18 +523,16 @@
@Test
public void testBackToActivity() throws RemoteException {
- final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
+ final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(mContext,
mAnimationBackground);
- verifySystemBackBehavior(
- BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
}
@Test
public void testBackToTask() throws RemoteException {
final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
mAnimationBackground);
- verifySystemBackBehavior(
- BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
}
private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
@@ -497,12 +552,10 @@
/* enableAnimation = */ true,
/* isAnimationCallback = */ false);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
-
// Check that back start and progress is dispatched when first move.
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ doStartEvents(0, 100);
- simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationStart();
verify(callback).onBackStarted(any(BackMotionEvent.class));
verify(animationRunner).startAnimation(any(), any(), any(), any());
@@ -527,7 +580,16 @@
/* swipeEdge */ BackEvent.EDGE_LEFT);
}
- private void simulateRemoteAnimationStart(int type) throws RemoteException {
+ /**
+ * Simulate event sequence that starts a back navigation.
+ */
+ private void doStartEvents(int startX, int moveX) {
+ doMotionEvent(MotionEvent.ACTION_DOWN, startX);
+ mController.onPilferPointers();
+ doMotionEvent(MotionEvent.ACTION_MOVE, moveX);
+ }
+
+ private void simulateRemoteAnimationStart() throws RemoteException {
RemoteAnimationTarget animationTarget = createAnimationTarget();
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
@@ -539,12 +601,16 @@
private void simulateRemoteAnimationFinished() {
mController.onBackAnimationFinished();
- mController.finishBackNavigation();
+ mController.finishBackNavigation(/*triggerBack*/ true);
}
private void registerAnimation(int type) {
- mController.registerAnimation(type,
- new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ mController.registerAnimation(
+ type,
+ new BackAnimationRunner(
+ mAnimatorCallback,
+ mBackAnimationRunner,
+ mContext));
}
private void unregisterAnimation(int type) {
@@ -554,10 +620,11 @@
private static class ResultListener implements RemoteCallback.OnResultListener {
boolean mBackNavigationDone = false;
boolean mTriggerBack = false;
+
@Override
public void onResult(@Nullable Bundle result) {
mBackNavigationDone = true;
mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
}
- };
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index e7d4598..cebbbd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -102,15 +102,17 @@
// start animation with remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(
+ new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
} catch (RemoteException r) {
fail("onBackInvoked throw remote exception");
}
@@ -133,15 +135,17 @@
// start animation with remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(
+ new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
} catch (RemoteException r) {
fail("onBackCancelled throw remote exception");
}
@@ -155,11 +159,12 @@
// start animation without any remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
} catch (RemoteException r) {
fail("onBackInvoked throw remote exception");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
index 9088e89..bf07dcc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
@@ -71,17 +71,17 @@
linearTracker.update(touchX, 0f, velocityX, velocityY)
linearTracker.assertProgress(0f)
- // Restart
- touchX += 10f
- linearTracker.update(touchX, 0f, velocityX, velocityY)
- linearTracker.assertProgress(0f)
-
// Restarted, but pre-commit
val restartX = touchX
touchX += 10f
linearTracker.update(touchX, 0f, velocityX, velocityY)
linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE)
+ // continue restart within pre-commit
+ touchX += 10f
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE)
+
// Restarted, post-commit
touchX += 10f
linearTracker.setTriggerBack(true)
@@ -119,17 +119,17 @@
linearTracker.update(touchX, 0f, velocityX, velocityY)
linearTracker.assertProgress(0f)
- // Restart
- touchX -= 10f
- linearTracker.update(touchX, 0f, velocityX, velocityY)
- linearTracker.assertProgress(0f)
-
// Restarted, but pre-commit
val restartX = touchX
touchX -= 10f
linearTracker.update(touchX, 0f, velocityX, velocityY)
linearTracker.assertProgress((restartX - touchX) / target)
+ // continue restart within pre-commit
+ touchX -= 10f
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((restartX - touchX) / target)
+
// Restarted, post-commit
touchX -= 10f
linearTracker.setTriggerBack(true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 4a55429..dab762f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -111,6 +111,8 @@
@Mock
private BubbleLogger mBubbleLogger;
@Mock
+ private BubbleEducationController mEducationController;
+ @Mock
private ShellExecutor mMainExecutor;
@Captor
@@ -190,8 +192,8 @@
mMainExecutor);
mPositioner = new TestableBubblePositioner(mContext,
- mock(WindowManager.class));
- mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
+ mContext.getSystemService(WindowManager.class));
+ mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
mMainExecutor);
// Used by BubbleData to set lastAccessedTime
@@ -219,6 +221,22 @@
}
@Test
+ public void testAddAppBubble_setsTime() {
+ // Setup
+ mBubbleData.setListener(mListener);
+
+ // Test
+ assertThat(mAppBubble.getLastActivity()).isEqualTo(0);
+ setCurrentTime(1000);
+ mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+ false /* showInShade */);
+
+ // Verify
+ assertThat(mBubbleData.getBubbleInStackWithKey(mAppBubble.getKey())).isEqualTo(mAppBubble);
+ assertThat(mAppBubble.getLastActivity()).isEqualTo(1000);
+ }
+
+ @Test
public void testRemoveBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
@@ -385,6 +403,65 @@
assertOverflowChangedTo(ImmutableList.of());
}
+ /**
+ * Verifies that the update shouldn't show the user education, if the education is not required
+ */
+ @Test
+ public void test_shouldNotShowEducation() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(false);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isFalse();
+ }
+
+ /**
+ * Verifies that the update should show the user education, if the education is required
+ */
+ @Test
+ public void test_shouldShowEducation() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isTrue();
+ }
+
+ /**
+ * Verifies that the update shouldn't show the user education, if the education is required but
+ * the bubble should auto-expand
+ */
+ @Test
+ public void test_shouldShowEducation_shouldAutoExpand() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+ mBubbleData.setListener(mListener);
+ mBubbleA1.setShouldAutoExpand(true);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isFalse();
+ }
+
// COLLAPSED / ADD
/**
@@ -1101,7 +1178,7 @@
}
@Test
- public void test_removeAppBubble_skipsOverflow() {
+ public void test_removeAppBubble_overflows() {
String appBubbleKey = mAppBubble.getKey();
mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
false /* showInShade */);
@@ -1109,7 +1186,7 @@
mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isEqualTo(mAppBubble);
assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
similarity index 88%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index 0dc16f4..f5b0174 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.bubbles;
import static com.google.common.truth.Truth.assertThat;
@@ -27,10 +27,7 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.bubbles.BubbleOverflow;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.TestableBubblePositioner;
+import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -56,7 +53,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class));
+ mPositioner = new TestableBubblePositioner(mContext,
+ mContext.getSystemService(WindowManager.class));
when(mBubbleController.getPositioner()).thenReturn(mPositioner);
when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index 58d9a64..6ebee73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -16,29 +16,22 @@
package com.android.wm.shell.bubbles;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.View.LAYOUT_DIRECTION_LTR;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
+import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import android.content.res.Configuration;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
@@ -48,36 +41,20 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
/**
* Tests operations and the resulting state managed by {@link BubblePositioner}.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblePositionerTest extends ShellTestCase {
- private static final int MIN_WIDTH_FOR_TABLET = 600;
-
private BubblePositioner mPositioner;
- private Configuration mConfiguration;
-
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private WindowMetrics mWindowMetrics;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mConfiguration = spy(new Configuration());
- TestableResources testableResources = mContext.getOrCreateTestableResources();
- testableResources.overrideConfiguration(mConfiguration);
-
- mPositioner = new BubblePositioner(mContext, mWindowManager);
+ WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ mPositioner = new BubblePositioner(mContext, windowManager);
}
@Test
@@ -87,11 +64,11 @@
Rect availableRect = new Rect(screenBounds);
availableRect.inset(insets);
- new WindowManagerConfig()
+ DeviceConfig deviceConfig = new ConfigBuilder()
.setInsets(insets)
.setScreenBounds(screenBounds)
- .setUpConfig();
- mPositioner.update();
+ .build();
+ mPositioner.update(deviceConfig);
assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
assertThat(mPositioner.isLandscape()).isFalse();
@@ -101,16 +78,16 @@
@Test
public void testShowBubblesVertically_phonePortrait() {
- new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().build();
+ mPositioner.update(deviceConfig);
assertThat(mPositioner.showBubblesVertically()).isFalse();
}
@Test
public void testShowBubblesVertically_phoneLandscape() {
- new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build();
+ mPositioner.update(deviceConfig);
assertThat(mPositioner.isLandscape()).isTrue();
assertThat(mPositioner.showBubblesVertically()).isTrue();
@@ -118,8 +95,8 @@
@Test
public void testShowBubblesVertically_tablet() {
- new WindowManagerConfig().setLargeScreen().setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
+ mPositioner.update(deviceConfig);
assertThat(mPositioner.showBubblesVertically()).isTrue();
}
@@ -127,8 +104,8 @@
/** If a resting position hasn't been set, calling it will return the default position. */
@Test
public void testGetRestingPosition_returnsDefaultPosition() {
- new WindowManagerConfig().setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().build();
+ mPositioner.update(deviceConfig);
PointF restingPosition = mPositioner.getRestingPosition();
PointF defaultPosition = mPositioner.getDefaultStartPosition();
@@ -139,8 +116,8 @@
/** If a resting position has been set, it'll return that instead of the default position. */
@Test
public void testGetRestingPosition_returnsRestingPosition() {
- new WindowManagerConfig().setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().build();
+ mPositioner.update(deviceConfig);
PointF restingPosition = new PointF(100, 100);
mPositioner.setRestingPosition(restingPosition);
@@ -151,8 +128,8 @@
/** Test that the default resting position on phone is in upper left. */
@Test
public void testGetRestingPosition_bubble_onPhone() {
- new WindowManagerConfig().setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().build();
+ mPositioner.update(deviceConfig);
RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
@@ -164,8 +141,8 @@
@Test
public void testGetRestingPosition_bubble_onPhone_RTL() {
- new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build();
+ mPositioner.update(deviceConfig);
RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
@@ -178,8 +155,8 @@
/** Test that the default resting position on tablet is middle left. */
@Test
public void testGetRestingPosition_chatBubble_onTablet() {
- new WindowManagerConfig().setLargeScreen().setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
+ mPositioner.update(deviceConfig);
RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
@@ -191,9 +168,8 @@
@Test
public void testGetRestingPosition_chatBubble_onTablet_RTL() {
- new WindowManagerConfig().setLargeScreen().setLayoutDirection(
- LAYOUT_DIRECTION_RTL).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
+ mPositioner.update(deviceConfig);
RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
@@ -206,8 +182,8 @@
/** Test that the default resting position on tablet is middle right. */
@Test
public void testGetDefaultPosition_appBubble_onTablet() {
- new WindowManagerConfig().setLargeScreen().setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
+ mPositioner.update(deviceConfig);
RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
@@ -219,9 +195,8 @@
@Test
public void testGetRestingPosition_appBubble_onTablet_RTL() {
- new WindowManagerConfig().setLargeScreen().setLayoutDirection(
- LAYOUT_DIRECTION_RTL).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
+ mPositioner.update(deviceConfig);
RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
@@ -233,9 +208,8 @@
@Test
public void testHasUserModifiedDefaultPosition_false() {
- new WindowManagerConfig().setLargeScreen().setLayoutDirection(
- LAYOUT_DIRECTION_RTL).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
+ mPositioner.update(deviceConfig);
assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
@@ -246,9 +220,8 @@
@Test
public void testHasUserModifiedDefaultPosition_true() {
- new WindowManagerConfig().setLargeScreen().setLayoutDirection(
- LAYOUT_DIRECTION_RTL).setUpConfig();
- mPositioner.update();
+ DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
+ mPositioner.update(deviceConfig);
assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
@@ -257,6 +230,303 @@
assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
}
+ @Test
+ public void testGetExpandedViewHeight_max() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT);
+ }
+
+ @Test
+ public void testGetExpandedViewHeight_customHeight_valid() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ final int minHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_expanded_default_height);
+ Bubble bubble = new Bubble("key",
+ mock(ShortcutInfo.class),
+ minHeight + 100 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor(),
+ mock(Bubbles.BubbleMetadataFlagListener.class));
+
+ // Ensure the height is the same as the desired value
+ assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(
+ bubble.getDesiredHeight(mContext));
+ }
+
+
+ @Test
+ public void testGetExpandedViewHeight_customHeight_tooSmall() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Bubble bubble = new Bubble("key",
+ mock(ShortcutInfo.class),
+ 10 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor(),
+ mock(Bubbles.BubbleMetadataFlagListener.class));
+
+ // Ensure the height is the same as the minimum value
+ final int minHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_expanded_default_height);
+ assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight);
+ }
+
+ @Test
+ public void testGetMaxExpandedViewHeight_onLargeTablet() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ int manageButtonHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+ int pointerWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_pointer_width);
+ int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R
+ .dimen.bubble_expanded_view_padding);
+ float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth
+ - expandedViewPadding * 2;
+ assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */)))
+ .isWithin(0.1f).of(expectedHeight);
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_largeScreen_true() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_largeScreen_false() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_smallTablet_false() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setSmallTablet()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_phone_false() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+ }
+
+ @Test
+ public void testExpandedViewY_phoneLandscape() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height so it'll always be top aligned
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_phonePortrait() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // Always top aligned in phone portrait
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_smallTabletLandscape() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setSmallTablet()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_smallTabletPortrait() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setSmallTablet()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_largeScreenLandscape() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height which is always top aligned on landscape, large tablet
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_largeScreenPortrait() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ int manageButtonHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+ int manageButtonPlusMargin = manageButtonHeight + 2
+ * mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_manage_button_margin);
+ int pointerWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_pointer_width);
+
+ final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
+ - manageButtonPlusMargin
+ - mPositioner.getExpandedViewHeightForLargeScreen()
+ - pointerWidth;
+
+ // Bubbles are bottom aligned on portrait, large tablet
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(expectedExpandedViewY);
+ }
+
/**
* Calculates the Y position bubbles should be placed based on the config. Based on
* the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
@@ -286,50 +556,47 @@
* Sets up window manager to return config values based on what you need for the test.
* By default it sets up a portrait phone without any insets.
*/
- private class WindowManagerConfig {
+ private static class ConfigBuilder {
private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
private boolean mIsLargeScreen = false;
- private int mOrientation = ORIENTATION_PORTRAIT;
- private int mLayoutDirection = LAYOUT_DIRECTION_LTR;
+ private boolean mIsSmallTablet = false;
+ private boolean mIsLandscape = false;
+ private boolean mIsRtl = false;
private Insets mInsets = Insets.of(0, 0, 0, 0);
- public WindowManagerConfig setScreenBounds(Rect screenBounds) {
+ public ConfigBuilder setScreenBounds(Rect screenBounds) {
mScreenBounds = screenBounds;
return this;
}
- public WindowManagerConfig setLargeScreen() {
+ public ConfigBuilder setLargeScreen() {
mIsLargeScreen = true;
return this;
}
- public WindowManagerConfig setOrientation(int orientation) {
- mOrientation = orientation;
+ public ConfigBuilder setSmallTablet() {
+ mIsSmallTablet = true;
return this;
}
- public WindowManagerConfig setLayoutDirection(int layoutDirection) {
- mLayoutDirection = layoutDirection;
+ public ConfigBuilder setLandscape() {
+ mIsLandscape = true;
return this;
}
- public WindowManagerConfig setInsets(Insets insets) {
+ public ConfigBuilder setRtl() {
+ mIsRtl = true;
+ return this;
+ }
+
+ public ConfigBuilder setInsets(Insets insets) {
mInsets = insets;
return this;
}
- public void setUpConfig() {
- mConfiguration.smallestScreenWidthDp = mIsLargeScreen
- ? MIN_WIDTH_FOR_TABLET
- : MIN_WIDTH_FOR_TABLET - 1;
- mConfiguration.orientation = mOrientation;
-
- when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
- WindowInsets windowInsets = mock(WindowInsets.class);
- when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets);
- when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets);
- when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ private DeviceConfig build() {
+ return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl,
+ mScreenBounds, mInsets);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
new file mode 100644
index 0000000..f583321
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 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.bubbles
+
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Handler
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.statusbar.IStatusBarService
+import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
+import com.android.wm.shell.bubbles.properties.BubbleProperties
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewTransitions
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for loading / inflating views & icons for a bubble.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class BubbleViewInfoTest : ShellTestCase() {
+
+ private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
+ private lateinit var iconFactory: BubbleIconFactory
+ private lateinit var bubble: Bubble
+
+ private lateinit var bubbleController: BubbleController
+ private lateinit var mainExecutor: ShellExecutor
+ private lateinit var bubbleStackView: BubbleStackView
+ private lateinit var bubbleBarLayerView: BubbleBarLayerView
+
+ @Before
+ fun setup() {
+ metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
+ iconFactory = BubbleIconFactory(context,
+ 60,
+ 30,
+ Color.RED,
+ mContext.resources.getDimensionPixelSize(
+ R.dimen.importance_ring_stroke_width))
+
+ mainExecutor = TestShellExecutor()
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ val shellInit = ShellInit(mainExecutor)
+ val shellCommandHandler = ShellCommandHandler()
+ val shellController = ShellController(context, shellInit, shellCommandHandler,
+ mainExecutor)
+ val bubblePositioner = BubblePositioner(context, windowManager)
+ val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
+ BubbleEducationController(context), mainExecutor)
+ val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+ bubbleController = BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ mock<BubbleDataRepository>(),
+ mock<IStatusBarService>(),
+ windowManager,
+ WindowManagerShellWrapper(mainExecutor),
+ mock<UserManager>(),
+ mock<LauncherApps>(),
+ mock<BubbleLogger>(),
+ mock<TaskStackListenerImpl>(),
+ ShellTaskOrganizer(mainExecutor),
+ bubblePositioner,
+ mock<DisplayController>(),
+ null,
+ null,
+ mainExecutor,
+ mock<Handler>(),
+ mock<ShellExecutor>(),
+ mock<TaskViewTransitions>(),
+ mock<Transitions>(),
+ mock<SyncTransactionQueue>(),
+ mock<IWindowManager>(),
+ mock<BubbleProperties>())
+
+ bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
+ surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ }
+
+ @Test
+ fun testPopulate() {
+ bubble = createBubbleWithShortcut()
+ val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
+ bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+ assertThat(info!!).isNotNull()
+
+ assertThat(info.imageView).isNotNull()
+ assertThat(info.expandedView).isNotNull()
+ assertThat(info.bubbleBarExpandedView).isNull()
+
+ assertThat(info.shortcutInfo).isNotNull()
+ assertThat(info.appName).isNotEmpty()
+ assertThat(info.rawBadgeBitmap).isNotNull()
+ assertThat(info.dotPath).isNotNull()
+ assertThat(info.bubbleBitmap).isNotNull()
+ assertThat(info.badgeBitmap).isNotNull()
+ }
+
+ @Test
+ fun testPopulateForBubbleBar() {
+ bubble = createBubbleWithShortcut()
+ val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
+ bubbleController, bubbleBarLayerView, iconFactory, bubble,
+ false /* skipInflation */)
+ assertThat(info!!).isNotNull()
+
+ assertThat(info.imageView).isNull()
+ assertThat(info.expandedView).isNull()
+ assertThat(info.bubbleBarExpandedView).isNotNull()
+
+ assertThat(info.shortcutInfo).isNotNull()
+ assertThat(info.appName).isNotEmpty()
+ assertThat(info.rawBadgeBitmap).isNotNull()
+ assertThat(info.dotPath).isNotNull()
+ assertThat(info.bubbleBitmap).isNotNull()
+ assertThat(info.badgeBitmap).isNotNull()
+ }
+
+ @Test
+ fun testPopulate_invalidShortcutIcon() {
+ bubble = createBubbleWithShortcut()
+
+ // This eventually calls down to load the shortcut icon from the app, simulate an
+ // exception here if the app has an issue loading the shortcut icon; we default to
+ // the app icon in that case / none of the icons will be null.
+ val mockIconFactory = mock<BubbleIconFactory>()
+ whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
+ any())).doThrow(RuntimeException())
+
+ val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
+ bubbleController, bubbleBarLayerView, iconFactory, bubble,
+ true /* skipInflation */)
+ assertThat(info).isNotNull()
+
+ assertThat(info?.shortcutInfo).isNotNull()
+ assertThat(info?.appName).isNotEmpty()
+ assertThat(info?.rawBadgeBitmap).isNotNull()
+ assertThat(info?.dotPath).isNotNull()
+ assertThat(info?.bubbleBitmap).isNotNull()
+ assertThat(info?.badgeBitmap).isNotNull()
+ }
+
+ private fun createBubbleWithShortcut(): Bubble {
+ val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
+ return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
+ "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
+ mainExecutor, metadataFlagListener)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
index 44ff354..c4b9c9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
@@ -55,8 +55,6 @@
private BubblesNavBarMotionEventHandler mMotionEventHandler;
@Mock
- private WindowManager mWindowManager;
- @Mock
private Runnable mInterceptTouchRunnable;
@Mock
private MotionEventListener mMotionEventListener;
@@ -66,7 +64,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
- mWindowManager);
+ getContext().getSystemService(WindowManager.class));
mMotionEventHandler = new BubblesNavBarMotionEventHandler(getContext(), positioner,
mInterceptTouchRunnable, mMotionEventListener);
mMotionEventTime = SystemClock.uptimeMillis();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
new file mode 100644
index 0000000..f8eb50b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.transition.TransitionInfoBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests of {@link BubblesTransitionObserver}.
+ */
+@SmallTest
+public class BubblesTransitionObserverTest {
+
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private BubbleData mBubbleData;
+
+ @Mock
+ private IBinder mTransition;
+ @Mock
+ private SurfaceControl.Transaction mStartT;
+ @Mock
+ private SurfaceControl.Transaction mFinishT;
+
+ @Mock
+ private Bubble mBubble;
+
+ private BubblesTransitionObserver mTransitionObserver;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData);
+ }
+
+ @Test
+ public void testOnTransitionReady_open_collapsesStack() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_toFront_collapsesStack() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noTaskInfo_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Null task info
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */);
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noTaskId_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Invalid task id
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT,
+ createTaskInfo(INVALID_TASK_ID));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_notOpening_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Transits that aren't opening
+ TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_stackAnimating_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_stackNotExpanded_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noSelectedBubble_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_openingMatchesExpanded_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // What's moving to front is same as the opened bubble
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ return taskInfo;
+ }
+
+ private TransitionInfo createTransitionInfo(int changeType,
+ ActivityManager.RunningTaskInfo info) {
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ new WindowContainerToken(mock(IWindowContainerToken.class)),
+ mock(SurfaceControl.class));
+ change.setMode(changeType);
+ change.setTaskInfo(info);
+
+ return new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(change).build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 335222e..c1ff260 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -66,7 +66,8 @@
public void setUp() throws Exception {
super.setUp();
- mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class));
+ mPositioner = new BubblePositioner(getContext(),
+ getContext().getSystemService(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
new Rect(0, 0, mDisplayWidth, mDisplayHeight));
@@ -105,7 +106,7 @@
verify(afterExpand).run();
Runnable afterCollapse = mock(Runnable.class);
- mExpandedController.collapseBackToStack(mExpansionPoint, afterCollapse);
+ mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse);
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
index 991913a..f660987 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
@@ -50,9 +50,6 @@
private ExpandedViewAnimationController mController;
@Mock
- private WindowManager mWindowManager;
-
- @Mock
private BubbleExpandedView mMockExpandedView;
@Before
@@ -60,7 +57,7 @@
MockitoAnnotations.initMocks(this);
TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
- mWindowManager);
+ getContext().getSystemService(WindowManager.class));
mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner);
mController.setExpandedView(mMockExpandedView);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
index 31fafca..0c22908 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
@@ -313,7 +313,8 @@
bubbleCountSupplier,
onBubbleAnimatedOutAction,
onStackAnimationFinished,
- new TestableBubblePositioner(mContext, mock(WindowManager.class)));
+ new TestableBubblePositioner(mContext,
+ mContext.getSystemService(WindowManager.class)));
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 9f0d89b..5237585 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -27,15 +27,13 @@
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -66,7 +64,7 @@
@Before
fun setup() {
- launcherApps = mock(LauncherApps::class.java)
+ launcherApps = mock<LauncherApps>()
repository = BubbleVolatileRepository(launcherApps)
}
@@ -98,7 +96,7 @@
repository.addBubbles(user11.identifier, listOf(bubble12))
assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier))
- Mockito.verifyNoMoreInteractions(launcherApps)
+ verifyNoMoreInteractions(launcherApps)
}
@Test
@@ -167,9 +165,8 @@
assertThat(ret).isTrue() // bubbles were removed
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -184,9 +181,8 @@
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -200,9 +196,8 @@
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble2, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -219,9 +214,8 @@
user11.identifier))
assertThat(ret).isFalse() // bubbles were NOT removed
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -237,9 +231,8 @@
assertThat(ret).isTrue() // bubbles were removed
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
// User 11 bubbles should still be here
assertThat(repository.getEntities(user11.identifier).toList())
@@ -261,9 +254,8 @@
// bubble2 is the work profile bubble and should be removed
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
// User 11 bubbles should still be here
assertThat(repository.getEntities(user11.identifier).toList())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 145c8f0..636c632 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -69,7 +69,7 @@
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
- splitWindowManager.init(mSplitLayout, new InsetsState());
+ splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
mDividerView = spy((DividerView) splitWindowManager.getDividerView());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index fe2da5d..56d0f8e1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -19,6 +19,10 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -130,7 +134,7 @@
@Test
public void testSetDivideRatio() {
mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
- mSplitLayout.setDivideRatio(0.5f);
+ mSplitLayout.setDivideRatio(SNAP_TO_50_50);
assertThat(mSplitLayout.getDividePosition()).isEqualTo(
mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
@@ -146,7 +150,7 @@
public void testSnapToDismissStart() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+ SNAP_TO_START_AND_DISMISS);
mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
@@ -158,7 +162,7 @@
public void testSnapToDismissEnd() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+ SNAP_TO_END_AND_DISMISS);
mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
new file mode 100644
index 0000000..fe26110
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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.common.split
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SplitScreenConstantsTest {
+
+ /**
+ * Ensures that some important constants are not changed from their set values. These values
+ * are persisted in user-defined app pairs, and changing them will break things.
+ */
+ @Test
+ fun shouldKeepExistingConstantValues() {
+ assertEquals(
+ "the value of SPLIT_POSITION_TOP_OR_LEFT should be 0",
+ 0,
+ SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT,
+ )
+ assertEquals(
+ "the value of SPLIT_POSITION_BOTTOM_OR_RIGHT should be 1",
+ 1,
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ )
+ assertEquals(
+ "the value of SNAP_TO_30_70 should be 0",
+ 0,
+ SplitScreenConstants.SNAP_TO_30_70,
+ )
+ assertEquals(
+ "the value of SNAP_TO_50_50 should be 1",
+ 1,
+ SplitScreenConstants.SNAP_TO_50_50,
+ )
+ assertEquals(
+ "the value of SNAP_TO_70_30 should be 2",
+ 2,
+ SplitScreenConstants.SNAP_TO_70_30,
+ )
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 2e5078d..150aa13 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -59,7 +59,7 @@
@Test
@UiThreadTest
public void testInitRelease() {
- mSplitWindowManager.init(mSplitLayout, new InsetsState());
+ mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
mSplitWindowManager.release(null /* t */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index f85d707..fef81af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -16,8 +16,8 @@
package com.android.wm.shell.compatui;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -34,8 +34,8 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
-import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
@@ -688,8 +688,8 @@
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.displayId = displayId;
- taskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
taskInfo.isVisible = isVisible;
taskInfo.isFocused = isFocused;
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 3bce2b8..23a4e39 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -16,10 +16,10 @@
package com.android.wm.shell.compatui;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -28,8 +28,8 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
-import android.app.TaskInfo.CameraCompatControlState;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -219,8 +219,8 @@
@CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 4c837e6..d4b97ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -16,10 +16,10 @@
package com.android.wm.shell.compatui;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -464,11 +465,11 @@
}
private static TaskInfo createTaskInfo(boolean hasSizeCompat,
- @TaskInfo.CameraCompatControlState int cameraCompatControlState) {
+ @AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 9200b3c9..a60a1cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -477,7 +477,7 @@
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.userId = userId;
taskInfo.taskId = TASK_ID;
- taskInfo.topActivityEligibleForLetterboxEducation = eligible;
+ taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = eligible;
taskInfo.configuration.windowConfiguration.setBounds(bounds);
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index f460d1b..38d6ea1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.compatui;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -25,8 +25,8 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
-import android.app.TaskInfo.CameraCompatControlState;
import android.content.ComponentName;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -147,8 +147,8 @@
@CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 5a4d6c8..9fe2cb1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.compatui;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -33,6 +36,7 @@
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -108,7 +112,7 @@
MockitoAnnotations.initMocks(this);
mExecutor = new TestShellExecutor();
mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
- false, /* topActivityBoundsLetterboxed */ true);
+ false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
@@ -179,7 +183,7 @@
// No diff
clearInvocations(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
- true, /* topActivityBoundsLetterboxed */ true);
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
verify(mWindowManager, never()).updateSurfacePosition();
@@ -200,7 +204,24 @@
clearInvocations(mWindowManager);
clearInvocations(mLayout);
taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
- false, /* topActivityBoundsLetterboxed */ true);
+ false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+ assertFalse(
+ mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+ verify(mWindowManager).release();
+
+ // Recreate button
+ clearInvocations(mWindowManager);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager).release();
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Change has no launcher category and is not main intent, dispose the component
+ clearInvocations(mWindowManager);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_USB_STATE, "");
assertFalse(
mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
verify(mWindowManager).release();
@@ -217,7 +238,7 @@
// inflated
clearInvocations(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
- false, /* topActivityBoundsLetterboxed */ true);
+ false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager, never()).inflateLayout();
@@ -225,7 +246,7 @@
// Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
clearInvocations(mWindowManager);
taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
- true, /* topActivityBoundsLetterboxed */ true);
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager).inflateLayout();
@@ -304,7 +325,7 @@
clearInvocations(mWindowManager);
spyOn(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
- true, /* topActivityBoundsLetterboxed */ true);
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
// User aspect ratio settings button has not yet been shown.
doReturn(false).when(mUserAspectRatioButtonShownChecker).get();
@@ -378,13 +399,15 @@
}
private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
- boolean topActivityBoundsLetterboxed) {
+ boolean topActivityBoundsLetterboxed, String action, String category) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton;
- taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+ taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
+ eligibleForUserAspectRatioButton;
+ taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ taskInfo.baseIntent = new Intent(action).addCategory(category);
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
deleted file mode 100644
index 605a762..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ /dev/null
@@ -1,531 +0,0 @@
-/*
- * 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.desktopmode;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
-import android.window.WindowContainerTransaction.HierarchyOp;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.wm.shell.MockToken;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DesktopModeControllerTest extends ShellTestCase {
-
- private static final int SECOND_DISPLAY = 2;
-
- @Mock
- private ShellController mShellController;
- @Mock
- private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock
- private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
- @Mock
- private ShellExecutor mTestExecutor;
- @Mock
- private Handler mMockHandler;
- @Mock
- private Transitions mTransitions;
- private DesktopModeController mController;
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
- private ShellInit mShellInit;
- private StaticMockitoSession mMockitoSession;
-
- @Before
- public void setUp() {
- mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
- when(DesktopModeStatus.isActive(any())).thenReturn(true);
-
- mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
-
- mDesktopModeTaskRepository = new DesktopModeTaskRepository();
-
- mController = createController();
-
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
-
- mShellInit.init();
- clearInvocations(mShellTaskOrganizer);
- clearInvocations(mRootTaskDisplayAreaOrganizer);
- clearInvocations(mTransitions);
- }
-
- @After
- public void tearDown() {
- mMockitoSession.finishMocking();
- }
-
- @Test
- public void instantiate_addInitCallback() {
- verify(mShellInit).addInitCallback(any(), any());
- }
-
- @Test
- public void instantiate_flagOff_doNotAddInitCallback() {
- when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
- clearInvocations(mShellInit);
-
- createController();
-
- verify(mShellInit, never()).addInitCallback(any(), any());
- }
-
- @Test
- public void testDesktopModeEnabled_rootTdaSetToFreeform() {
- DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
- mController.updateDesktopModeActive(true);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 1 change: Root TDA windowing mode
- assertThat(wct.getChanges().size()).isEqualTo(1);
- // Verify WCT has a change for setting windowing mode to freeform
- Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(change).isNotNull();
- assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
- }
-
- @Test
- public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
- DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
- mController.updateDesktopModeActive(false);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 1 change: Root TDA windowing mode
- assertThat(wct.getChanges().size()).isEqualTo(1);
- // Verify WCT has a change for setting windowing mode to fullscreen
- Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(change).isNotNull();
- assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
- }
-
- @Test
- public void testDesktopModeEnabled_windowingModeCleared() {
- createMockDisplayArea();
- RunningTaskInfo freeformTask = createFreeformTask();
- RunningTaskInfo fullscreenTask = createFullscreenTask();
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
- mController.updateDesktopModeActive(true);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 2 changes: Root TDA windowing mode and 1 task
- assertThat(wct.getChanges().size()).isEqualTo(2);
- // No changes for tasks that are not standard or freeform
- assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
- assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
- // Standard freeform task has windowing mode cleared
- Change change = wct.getChanges().get(freeformTask.token.asBinder());
- assertThat(change).isNotNull();
- assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
- createMockDisplayArea();
- RunningTaskInfo freeformTask = createFreeformTask();
- RunningTaskInfo fullscreenTask = createFullscreenTask();
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
- mController.updateDesktopModeActive(false);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 3 changes: Root TDA windowing mode and 2 tasks
- assertThat(wct.getChanges().size()).isEqualTo(3);
- // No changes to home task
- assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
- // Standard tasks have bounds cleared
- assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
- assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
- // Freeform standard tasks have windowing mode cleared
- assertThat(wct.getChanges().get(
- freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
- WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
- createMockDisplayArea();
- RunningTaskInfo fullscreenTask1 = createFullscreenTask();
- fullscreenTask1.isVisible = true;
- RunningTaskInfo fullscreenTask2 = createFullscreenTask();
- fullscreenTask2.isVisible = false;
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
-
- mController.updateDesktopModeActive(true);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // Check that there are hierarchy changes for home task and visible task
- assertThat(wct.getHierarchyOps()).hasSize(2);
- // First show home task
- HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
-
- // Then visible task on top of it
- HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
- // Set up two active tasks on desktop, task2 is on top of task1.
- RunningTaskInfo freeformTask1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */);
- RunningTaskInfo freeformTask2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
- freeformTask1);
- when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
- freeformTask2);
-
- // Run show desktop apps logic
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Check wct has reorder calls
- assertThat(wct.getHierarchyOps()).hasSize(2);
-
- // Task 1 appeared first, must be first reorder to top.
- HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
-
- // Task 2 appeared last, must be last reorder to top.
- HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
- final RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
- final RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Check wct has reorder calls
- assertThat(wct.getHierarchyOps()).hasSize(2);
- // Task 1 appeared first, must be first reorder to top.
- HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
-
- // Task 2 appeared last, must be last reorder to top.
- HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_someAppsInvisible_reordersAll() {
- final RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
- final RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Both tasks should be reordered to top, even if one was already visible.
- assertThat(wct.getHierarchyOps()).hasSize(2);
- final HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
- final HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
- RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY);
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn(
- taskDefaultDisplay);
-
- RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY);
- mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn(
- taskSecondDisplay);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- assertThat(wct.getHierarchyOps()).hasSize(1);
- HierarchyOp op = wct.getHierarchyOps().get(0);
- assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder());
- }
-
- @Test
- public void testGetVisibleTaskCount_noTasks_returnsZero() {
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
- RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
-
- RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
-
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
- RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
-
- RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- false /* visible */);
-
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
- RunningTaskInfo taskDefaultDisplay = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY,
- taskDefaultDisplay.taskId,
- true /* visible */);
-
- RunningTaskInfo taskSecondDisplay = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY,
- taskSecondDisplay.taskId,
- true /* visible */);
-
- assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1);
- }
-
- @Test
- public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
- when(DesktopModeStatus.isActive(any())).thenReturn(false);
- WindowContainerTransaction wct = mController.handleRequest(
- new Binder(),
- new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- assertThat(wct).isNull();
- }
-
- @Test
- public void testHandleTransitionRequest_unsupportedTransit_returnsNull() {
- WindowContainerTransaction wct = mController.handleRequest(
- new Binder(),
- new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
- assertThat(wct).isNull();
- }
-
- @Test
- public void testHandleTransitionRequest_notFreeform_returnsNull() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- WindowContainerTransaction wct = mController.handleRequest(
- new Binder(),
- new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
- assertThat(wct).isNull();
- }
-
- @Test
- public void testHandleTransitionRequest_taskOpen_returnsWct() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- WindowContainerTransaction wct = mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
- assertThat(wct).isNotNull();
- }
-
- @Test
- public void testHandleTransitionRequest_taskToFront_returnsWct() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- WindowContainerTransaction wct = mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
- assertThat(wct).isNotNull();
- }
-
- @Test
- public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
- verifyZeroInteractions(mTransitions);
- }
-
- private DesktopModeController createController() {
- return new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
- mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
- }
-
- private DisplayAreaInfo createMockDisplayArea() {
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().token(),
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
- return displayAreaInfo;
- }
-
- private WindowContainerTransaction getDesktopModeSwitchTransaction() {
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
- } else {
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
- }
- return arg.getValue();
- }
-
- private WindowContainerTransaction getBringAppsToFrontTransaction() {
- final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any());
- } else {
- verify(mShellTaskOrganizer).applyTransaction(arg.capture());
- }
- return arg.getValue();
- }
-
- private void assertThatBoundsCleared(Change change) {
- assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
- assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
- }
-
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5d87cf8..94c862b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -28,10 +28,10 @@
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
-import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DisplayAreaInfo
+import android.window.RemoteTransition
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -43,6 +43,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
@@ -52,11 +53,18 @@
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE
+import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -67,6 +75,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
@@ -92,13 +101,17 @@
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler
+ @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
@Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
+ @Mock lateinit var splitScreenController: SplitScreenController
+ @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+ private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private val shellExecutor = TestShellExecutor()
// Mock running tasks are registered here so we can get the list from mock shell task organizer
@@ -107,7 +120,7 @@
@Before
fun setUp() {
mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
- whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true)
+ whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
shellInit = Mockito.spy(ShellInit(testExecutor))
desktopModeTaskRepository = DesktopModeTaskRepository()
@@ -116,8 +129,13 @@
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
controller = createController()
+ controller.setSplitScreenController(splitScreenController)
shellInit.init()
+
+ val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+ verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
+ recentsTransitionStateListener = captor.value
}
private fun createController(): DesktopTasksController {
@@ -134,8 +152,10 @@
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
mToggleResizeDesktopTaskTransitionHandler,
+ dragToDesktopTransitionHandler,
desktopModeTaskRepository,
launchAdjacentController,
+ recentsTransitionHandler,
shellExecutor
)
}
@@ -154,7 +174,7 @@
@Test
fun instantiate_flagOff_doNotAddInitCallback() {
- whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false)
+ whenever(DesktopModeStatus.isEnabled()).thenReturn(false)
clearInvocations(shellInit)
createController()
@@ -170,9 +190,10 @@
markTaskHidden(task1)
markTaskHidden(task2)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -188,9 +209,10 @@
markTaskVisible(task1)
markTaskVisible(task2)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -206,9 +228,10 @@
markTaskHidden(task1)
markTaskVisible(task2)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -220,9 +243,10 @@
fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
val homeTask = setUpHomeTask()
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, homeTask)
}
@@ -236,9 +260,10 @@
markTaskHidden(taskDefaultDisplay)
markTaskHidden(taskSecondDisplay)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(2)
// Expect order to be from bottom: home, task
wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
@@ -341,11 +366,35 @@
}
@Test
+ fun moveToDesktop_splitTaskExitsSplit() {
+ val task = setUpSplitScreenTask()
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ )
+ }
+
+ @Test
+ fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
+ val task = setUpFullscreenTask()
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ )
+ }
+
+ @Test
fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToFullscreen(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
}
@@ -354,15 +403,15 @@
fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToFullscreen(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
- controller.moveToFullscreen(999)
+ controller.moveToFullscreen(999, desktopModeWindowDecoration)
verifyWCTNotExecuted()
}
@@ -371,9 +420,9 @@
val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToFullscreen(taskDefaultDisplay)
+ controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestExitDesktopWct()) {
assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
}
@@ -386,7 +435,7 @@
controller.moveTaskToFront(task1)
- val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+ val wct = getLatestWct(type = TRANSIT_TO_FRONT)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, task1)
}
@@ -411,7 +460,7 @@
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(1)
assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
assertThat(hierarchyOps[0].isReparent).isTrue()
@@ -433,7 +482,7 @@
val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(1)
assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
assertThat(hierarchyOps[0].isReparent).isTrue()
@@ -629,6 +678,20 @@
}
@Test
+ fun handleRequest_recentsAnimationRunning_returnNull() {
+ // Set up a visible freeform task so a fullscreen task should be converted to freeform
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onAnimationStateChanged(true)
+
+ // Open a fullscreen task, check that it does not result in a WCT with changes to it
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
fun stashDesktopApps_stateUpdates() {
whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
@@ -695,6 +758,14 @@
return task
}
+ private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createSplitScreenTask(displayId)
+ whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
private fun markTaskVisible(task: RunningTaskInfo) {
desktopModeTaskRepository.updateVisibleFreeformTasks(
task.displayId,
@@ -712,11 +783,16 @@
}
private fun getLatestWct(
- @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ handlerClass: Class<out TransitionHandler>? = null
): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull())
+ if (handlerClass == null) {
+ verify(transitions).startTransition(eq(type), arg.capture(), isNull())
+ } else {
+ verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
+ }
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
}
@@ -733,6 +809,17 @@
return arg.value
}
+ private fun getLatestExitDesktopWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(exitDesktopTransitionHandler)
+ .startTransition(eq(TRANSIT_EXIT_DESKTOP_MODE), arg.capture(), any(), any())
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ }
+ return arg.value
+ }
+
private fun verifyWCTNotExecuted() {
if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 29a757c..2f6f320 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -21,6 +21,7 @@
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.view.Display.DEFAULT_DISPLAY
import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -45,12 +46,25 @@
@JvmOverloads
fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
+ @JvmStatic
+ @JvmOverloads
+ fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setLastActiveTime(100)
+ .build()
}
/** Create a new home task */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
new file mode 100644
index 0000000..3bc90ad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -0,0 +1,248 @@
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WindowingMode
+import android.graphics.PointF
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionInfo.FLAG_IS_WALLPAPER
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import java.util.function.Supplier
+import junit.framework.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+/** Tests of [DragToDesktopTransitionHandler]. */
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DragToDesktopTransitionHandlerTest : ShellTestCase() {
+
+ @Mock private lateinit var transitions: Transitions
+ @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var splitScreenController: SplitScreenController
+
+ private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
+
+ private lateinit var handler: DragToDesktopTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler =
+ DragToDesktopTransitionHandler(
+ context,
+ transitions,
+ taskDisplayAreaOrganizer,
+ transactionSupplier
+ )
+ .apply { setSplitScreenController(splitScreenController) }
+ }
+
+ @Test
+ fun startDragToDesktop_animateDragWhenReady() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+ // Simulate transition is started.
+ val transition = startDragToDesktopTransition(task, dragAnimator)
+
+ // Now it's ready to animate.
+ handler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ verify(dragAnimator).startAnimation()
+ }
+
+ @Test
+ fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+ // Simulate transition is started and is ready to animate.
+ val transition = startDragToDesktopTransition(task, dragAnimator)
+
+ handler.cancelDragToDesktopTransition()
+
+ handler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ // Don't even animate the "drag" since it was already cancelled.
+ verify(dragAnimator, never()).startAnimation()
+ // Instead, start the cancel transition.
+ verify(transitions)
+ .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler))
+ }
+
+ @Test
+ fun startDragToDesktop_aborted_finishDropped() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+ // Simulate transition is started.
+ val transition = startDragToDesktopTransition(task, dragAnimator)
+ // But the transition was aborted.
+ handler.onTransitionConsumed(transition, aborted = true, mock())
+
+ // Attempt to finish the failed drag start.
+ handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+ // Should not be attempted and state should be reset.
+ verify(transitions, never())
+ .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
+ assertFalse(handler.inProgress)
+ }
+
+ @Test
+ fun startDragToDesktop_aborted_cancelDropped() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+ // Simulate transition is started.
+ val transition = startDragToDesktopTransition(task, dragAnimator)
+ // But the transition was aborted.
+ handler.onTransitionConsumed(transition, aborted = true, mock())
+
+ // Attempt to finish the failed drag start.
+ handler.cancelDragToDesktopTransition()
+
+ // Should not be attempted and state should be reset.
+ assertFalse(handler.inProgress)
+ }
+
+ @Test
+ fun cancelDragToDesktop_startWasReady_cancel() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+ whenever(dragAnimator.position).thenReturn(PointF())
+ // Simulate transition is started and is ready to animate.
+ val transition = startDragToDesktopTransition(task, dragAnimator)
+ handler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ // Then user cancelled after it had already started.
+ handler.cancelDragToDesktopTransition()
+
+ // Cancel animation should run since it had already started.
+ verify(dragAnimator).endAnimator()
+ }
+
+ @Test
+ fun cancelDragToDesktop_startWasNotReady_animateCancel() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+ // Simulate transition is started and is ready to animate.
+ startDragToDesktopTransition(task, dragAnimator)
+
+ // Then user cancelled before the transition was ready and animated.
+ handler.cancelDragToDesktopTransition()
+
+ // No need to animate the cancel since the start animation couldn't even start.
+ verifyZeroInteractions(dragAnimator)
+ }
+
+ private fun startDragToDesktopTransition(
+ task: RunningTaskInfo,
+ dragAnimator: MoveToDesktopAnimator
+ ): IBinder {
+ val token = mock<IBinder>()
+ whenever(
+ transitions.startTransition(
+ eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ )
+ .thenReturn(token)
+ handler.startDragToDesktopTransition(task.taskId, dragAnimator, mock())
+ return token
+ }
+
+ private fun createTask(
+ @WindowingMode windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+ isHome: Boolean = false,
+ ): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setActivityType(if (isHome) ACTIVITY_TYPE_HOME else ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(windowingMode)
+ .build()
+ .also {
+ whenever(splitScreenController.isTaskInSplitScreen(it.taskId))
+ .thenReturn(windowingMode == WINDOWING_MODE_MULTI_WINDOW)
+ }
+ }
+
+ private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo {
+ return TransitionInfo(type, 0 /* flags */).apply {
+ addChange( // Home.
+ TransitionInfo.Change(mock(), mock()).apply {
+ parent = null
+ taskInfo =
+ TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ flags = flags or FLAG_IS_WALLPAPER
+ }
+ )
+ addChange( // Dragged Task.
+ TransitionInfo.Change(mock(), mock()).apply {
+ parent = null
+ taskInfo = draggedTask
+ }
+ )
+ addChange( // Wallpaper.
+ TransitionInfo.Change(mock(), mock()).apply {
+ parent = null
+ taskInfo = null
+ flags = flags or FLAG_IS_WALLPAPER
+ }
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
deleted file mode 100644
index 772d97d..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2023 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.desktopmode;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.IWindowContainerToken;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.function.Supplier;
-
-/** Tests of {@link com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler} */
-@SmallTest
-public class EnterDesktopTaskTransitionHandlerTest {
-
- @Mock
- private Transitions mTransitions;
- @Mock
- IBinder mToken;
- @Mock
- Supplier<SurfaceControl.Transaction> mTransactionFactory;
- @Mock
- SurfaceControl.Transaction mStartT;
- @Mock
- SurfaceControl.Transaction mFinishT;
- @Mock
- SurfaceControl.Transaction mAnimationT;
- @Mock
- Transitions.TransitionFinishCallback mTransitionFinishCallback;
- @Mock
- ShellExecutor mExecutor;
- @Mock
- SurfaceControl mSurfaceControl;
- @Mock
- MoveToDesktopAnimator mMoveToDesktopAnimator;
- @Mock
- PointF mPosition;
-
- private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- doReturn(mExecutor).when(mTransitions).getMainExecutor();
- doReturn(mAnimationT).when(mTransactionFactory).get();
- doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
-
- mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
- mTransactionFactory);
- }
-
- @Test
- public void testEnterFreeformAnimation() {
- final int taskId = 1;
- WindowContainerTransaction wct = new WindowContainerTransaction();
- doReturn(mToken).when(mTransitions)
- .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
- mEnterDesktopTaskTransitionHandler);
- doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
-
- mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
- mMoveToDesktopAnimator, null);
-
- TransitionInfo.Change change =
- createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE,
- change);
-
-
- assertTrue(mEnterDesktopTaskTransitionHandler
- .startAnimation(mToken, info, mStartT, mFinishT, mTransitionFinishCallback));
-
- verify(mStartT).setWindowCrop(mSurfaceControl, null);
- verify(mStartT).apply();
- }
-
- @Test
- public void testTransitEnterDesktopModeAnimation() throws Throwable {
- final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE;
- final int taskId = 1;
- WindowContainerTransaction wct = new WindowContainerTransaction();
- doReturn(mToken).when(mTransitions)
- .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
-
- TransitionInfo.Change change =
- createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
- change.setEndAbsBounds(new Rect(0, 0, 1, 1));
- TransitionInfo info = createTransitionInfo(
- Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change);
-
- runOnUiThread(() -> {
- try {
- assertTrue(mEnterDesktopTaskTransitionHandler
- .startAnimation(mToken, info, mStartT, mFinishT,
- mTransitionFinishCallback));
- } catch (Exception e) {
- throw new AssertionFailedError(e.getMessage());
- }
- });
-
- verify(mStartT).setWindowCrop(mSurfaceControl, change.getEndAbsBounds().width(),
- change.getEndAbsBounds().height());
- verify(mStartT).apply();
- }
-
- private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
- @WindowConfiguration.WindowingMode int windowingMode) {
- final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
- taskInfo.taskId = taskId;
- taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
- final TransitionInfo.Change change = new TransitionInfo.Change(
- new WindowContainerToken(mock(IWindowContainerToken.class)), mSurfaceControl);
- change.setMode(type);
- change.setTaskInfo(taskInfo);
- return change;
- }
-
- private static TransitionInfo createTransitionInfo(
- @WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) {
- TransitionInfo info = new TransitionInfo(type, 0);
- info.addChange(change);
- return info;
- }
-
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 527dc01..1b347e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -204,6 +204,7 @@
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+ doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
@@ -219,6 +220,7 @@
@Test
public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
@@ -239,6 +241,7 @@
@Test
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mPortraitDisplayLayout, mActivityClipData);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 69f664a..499e339 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -37,8 +37,8 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index d34e27b..db98abb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
+import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -60,6 +61,9 @@
/** The minimum possible size of the override min size's width or height */
private static final int OVERRIDABLE_MIN_SIZE = 40;
+ /** The margin of error for floating point results. */
+ private static final float MARGIN_OF_ERROR = 0.05f;
+
private PipBoundsState mPipBoundsState;
private SizeSpecSource mSizeSpecSource;
private ComponentName mTestComponentName1;
@@ -88,6 +92,27 @@
}
@Test
+ public void testBoundsScale() {
+ mPipBoundsState.setMaxSize(300, 300);
+ mPipBoundsState.setBounds(new Rect(0, 0, 100, 100));
+
+ final int currentWidth = mPipBoundsState.getBounds().width();
+ final Point maxSize = mPipBoundsState.getMaxSize();
+ final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f);
+
+ // test for currentWidth < maxWidth
+ assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR);
+
+ // reset the bounds to be at the maximum size spec
+ mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y));
+ assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+
+ // reset the bounds to be over the maximum size spec
+ mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2));
+ assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+ }
+
+ @Test
public void testSetReentryState() {
final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 4e2b7f6..800f9e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -66,6 +66,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -283,7 +284,7 @@
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
.round(any(), any(), anyBoolean());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
- .scale(any(), any(), any(), any(), anyFloat());
+ .scale(any(), any(), any(), ArgumentMatchers.<Rect>any(), anyFloat());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
.alpha(any(), any(), anyFloat());
doNothing().when(mMockPipSurfaceTransactionHelper).onDensityOrFontScaleChanged(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 6777a5b..9719ba8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -28,11 +28,13 @@
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -98,6 +100,9 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(R.bool.config_pipEnablePinchResize, true);
+
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index c40cd406..45f6c8c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -23,7 +23,9 @@
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
-import static org.junit.Assert.assertTrue;
+import static java.util.Collections.EMPTY_LIST;
+
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,7 +36,6 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.Log;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PipMediaController;
@@ -46,7 +47,9 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Unit tests for {@link TvPipActionsProvider}
@@ -69,35 +72,38 @@
@Mock
private PendingIntent mMockPendingIntent;
- private RemoteAction createRemoteAction(int identifier) {
+ private int mNumberOfRemoteActionsCreated = 0;
+
+ private RemoteAction createRemoteAction() {
+ final int identifier = mNumberOfRemoteActionsCreated++;
return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
}
private List<RemoteAction> createRemoteActions(int numberOfActions) {
List<RemoteAction> actions = new ArrayList<>();
for (int i = 0; i < numberOfActions; i++) {
- actions.add(createRemoteAction(i));
+ actions.add(createRemoteAction());
}
return actions;
}
- private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
- for (int i = 0; i < actions.size(); i++) {
- int type = actions.get(i).getActionType();
- if (type != actionTypes[i]) {
- Log.e(TAG, "Action at index " + i + ": found " + type
- + ", expected " + actionTypes[i]);
- return false;
- }
- }
- return true;
+ private void assertActionTypes(List<Integer> expected, List<Integer> actual) {
+ assertEquals(getActionTypesStrings(expected), getActionTypesStrings(actual));
+ }
+
+ private static List<String> getActionTypesStrings(List<Integer> actionTypes) {
+ return actionTypes.stream().map(a -> TvPipAction.getActionTypeString(a))
+ .collect(Collectors.toList());
+ }
+
+ private List<Integer> getActionsTypes() {
+ return mActionsProvider.getActionsList().stream().map(a -> a.getActionType())
+ .collect(Collectors.toList());
}
@Before
public void setUp() {
- if (!isTelevision()) {
- return;
- }
+ assumeTelevision();
MockitoAnnotations.initMocks(this);
mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
mMockSystemActionsHandler);
@@ -105,57 +111,51 @@
@Test
public void defaultSystemActions_regularPip() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ assertActionTypes(Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
}
@Test
public void defaultSystemActions_expandedPip() {
- assumeTelevision();
mActionsProvider.updateExpansionEnabled(true);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
}
@Test
public void expandedPip_enableExpansion_enable() {
- assumeTelevision();
// PiP has expanded PiP disabled.
- mActionsProvider.updateExpansionEnabled(false);
-
mActionsProvider.addListener(mMockListener);
mActionsProvider.updateExpansionEnabled(true);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
}
@Test
public void expandedPip_enableExpansion_disable() {
- assumeTelevision();
mActionsProvider.updateExpansionEnabled(true);
mActionsProvider.addListener(mMockListener);
mActionsProvider.updateExpansionEnabled(false);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
}
@Test
public void expandedPip_enableExpansion_AlreadyEnabled() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(true);
-
mActionsProvider.addListener(mMockListener);
mActionsProvider.updateExpansionEnabled(true);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
}
private void check_expandedPip_updateExpansionState(
@@ -167,8 +167,9 @@
mActionsProvider.addListener(mMockListener);
mActionsProvider.updatePipExpansionState(endExpansion);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
if (updateExpected) {
verify(mMockListener).onActionsChanged(0, 1, 3);
@@ -180,7 +181,6 @@
@Test
public void expandedPip_toggleExpansion_collapse() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ true,
/* endExpansion= */ false,
@@ -189,7 +189,6 @@
@Test
public void expandedPip_toggleExpansion_expand() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ false,
/* endExpansion= */ true,
@@ -198,7 +197,6 @@
@Test
public void expandedPiP_updateExpansionState_alreadyExpanded() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ true,
/* endExpansion= */ true,
@@ -207,7 +205,6 @@
@Test
public void expandedPiP_updateExpansionState_alreadyCollapsed() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ false,
/* endExpansion= */ false,
@@ -216,8 +213,6 @@
@Test
public void regularPiP_updateExpansionState_setCollapsed() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.updatePipExpansionState(/* expanded= */ false);
mActionsProvider.addListener(mMockListener);
@@ -229,153 +224,207 @@
@Test
public void customActions_added() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.addListener(mMockListener);
mActionsProvider.setAppActions(createRemoteActions(2), null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
}
@Test
public void customActions_replacedMore() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.setAppActions(createRemoteActions(2), null);
mActionsProvider.addListener(mMockListener);
mActionsProvider.setAppActions(createRemoteActions(3), null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_CUSTOM, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
}
@Test
public void customActions_replacedLess() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.setAppActions(createRemoteActions(2), null);
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(createRemoteActions(0), null);
+ mActionsProvider.setAppActions(EMPTY_LIST, null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
}
@Test
public void customCloseAdded() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = new ArrayList<>();
mActionsProvider.setAppActions(customActions, null);
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+ mActionsProvider.setAppActions(customActions, createRemoteAction());
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
}
@Test
public void customClose_matchesOtherCustomAction() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = createRemoteActions(2);
- RemoteAction customClose = createRemoteAction(/* id= */ 10);
+ RemoteAction customClose = createRemoteAction();
customActions.add(customClose);
mActionsProvider.addListener(mMockListener);
mActionsProvider.setAppActions(customActions, customClose);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
}
@Test
public void mediaActions_added_whileCustomActionsExist() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.setAppActions(createRemoteActions(2), null);
mActionsProvider.addListener(mMockListener);
mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
}
@Test
public void customActions_removed_whileMediaActionsExist() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
mActionsProvider.setAppActions(createRemoteActions(3), null);
- mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(createRemoteActions(0), null);
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(EMPTY_LIST, null);
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
}
@Test
public void customCloseOnly_mediaActionsShowing() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+ mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction());
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
}
@Test
public void customActions_showDisabledActions() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = createRemoteActions(2);
customActions.get(0).setEnabled(false);
mActionsProvider.setAppActions(customActions, null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
}
@Test
public void mediaActions_hideDisabledActions() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = createRemoteActions(2);
customActions.get(0).setEnabled(false);
mActionsProvider.onMediaActionsChanged(customActions);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_mediaActions() {
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.onMediaActionsChanged(customActions);
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_customActions() {
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.setAppActions(customActions, null);
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_customClose() {
+ mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction());
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_All() {
+ mActionsProvider.setAppActions(createRemoteActions(2), createRemoteAction());
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 82fe5f2..974539f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -53,9 +53,8 @@
@Before
public void setUp() {
- if (!isTelevision()) {
- return;
- }
+ assumeTelevision();
+
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
@@ -101,20 +100,22 @@
@Test
public void regularPip_defaultGravity() {
- assumeTelevision();
checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
}
@Test
+ public void regularPip_defaultTvPipGravity() {
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+ }
+
+ @Test
public void regularPip_defaultGravity_RTL() {
- assumeTelevision();
setRTL(true);
checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
}
@Test
public void updateGravity_expand_vertical() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
@@ -130,7 +131,6 @@
@Test
public void updateGravity_expand_horizontal() {
- assumeTelevision();
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
@@ -146,7 +146,6 @@
@Test
public void updateGravity_collapse() {
- assumeTelevision();
// Vertical expansion
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
@@ -164,7 +163,6 @@
@Test
public void updateGravity_collapse_RTL() {
- assumeTelevision();
setRTL(true);
// Horizontal expansion
@@ -177,7 +175,6 @@
@Test
public void updateGravity_expand_collapse() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
@@ -197,7 +194,6 @@
@Test
public void updateGravity_expand_move_collapse() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
@@ -230,7 +226,6 @@
@Test
public void updateGravity_move_regular_valid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
// clockwise
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
@@ -246,7 +241,6 @@
@Test
public void updateGravity_move_expanded_valid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipExpanded(true);
// Vertical expanded PiP.
@@ -264,7 +258,6 @@
@Test
public void updateGravity_move_regular_invalid() {
- assumeTelevision();
int gravity = Gravity.BOTTOM | Gravity.RIGHT;
mTvPipBoundsState.setTvPipGravity(gravity);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
@@ -288,7 +281,6 @@
@Test
public void updateGravity_move_expanded_invalid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipExpanded(true);
// Vertical expanded PiP.
@@ -318,7 +310,6 @@
@Test
public void previousCollapsedGravity_defaultValue() {
- assumeTelevision();
assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
mTvPipBoundsState.getDefaultGravity());
setRTL(true);
@@ -328,7 +319,6 @@
@Test
public void previousCollapsedGravity_changes_on_RTL() {
- assumeTelevision();
mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
setRTL(true);
assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
index 3a08d32..e26dc7c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -25,18 +25,24 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.os.Handler;
+import android.os.Looper;
import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnWindowFocusChangeListener;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.SystemWindows;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -50,28 +56,38 @@
@Mock
private SystemWindows mMockSystemWindows;
@Mock
- private SurfaceControl mMockPipLeash;
- @Mock
- private Handler mMockHandler;
- @Mock
- private TvPipActionsProvider mMockActionsProvider;
- @Mock
private TvPipMenuView mMockTvPipMenuView;
@Mock
private TvPipBackgroundView mMockTvPipBackgroundView;
+ private Handler mMainHandler;
private TvPipMenuController mTvPipMenuController;
+ private OnWindowFocusChangeListener mFocusChangeListener;
@Before
public void setUp() {
assumeTrue(isTelevision());
MockitoAnnotations.initMocks(this);
+ mMainHandler = new Handler(Looper.getMainLooper());
+
+ final ViewTreeObserver mockMenuTreeObserver = mock(ViewTreeObserver.class);
+ doReturn(mockMenuTreeObserver).when(mMockTvPipMenuView).getViewTreeObserver();
mTvPipMenuController = new TestTvPipMenuController();
mTvPipMenuController.setDelegate(mMockDelegate);
- mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
- mTvPipMenuController.attach(mMockPipLeash);
+ mTvPipMenuController.setTvPipActionsProvider(mock(TvPipActionsProvider.class));
+ mTvPipMenuController.attach(mock(SurfaceControl.class));
+ mFocusChangeListener = captureFocusChangeListener(mockMenuTreeObserver);
+ }
+
+ private OnWindowFocusChangeListener captureFocusChangeListener(
+ ViewTreeObserver mockTreeObserver) {
+ final ArgumentCaptor<OnWindowFocusChangeListener> focusChangeListenerCaptor =
+ ArgumentCaptor.forClass(OnWindowFocusChangeListener.class);
+ verify(mockTreeObserver).addOnWindowFocusChangeListener(
+ focusChangeListenerCaptor.capture());
+ return focusChangeListenerCaptor.getValue();
}
@Test
@@ -81,24 +97,25 @@
@Test
public void testSwitch_FromNoMenuMode_ToMoveMode() {
- showAndAssertMoveMenu();
+ showAndAssertMoveMenu(true);
}
@Test
public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
- showAndAssertAllActionsMenu();
+ showAndAssertAllActionsMenu(true);
}
@Test
public void testSwitch_FromMoveMode_ToAllActionsMode() {
- showAndAssertMoveMenu();
- showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu(true);
+ showAndAssertAllActionsMenu(false);
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testSwitch_FromAllActionsMode_ToMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu(true);
+ showAndAssertMoveMenu(false);
}
@Test
@@ -110,187 +127,282 @@
@Test
public void testCloseMenu_MoveMode() {
- showAndAssertMoveMenu();
+ showAndAssertMoveMenu(true);
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testCloseMenu_AllActionsMode() {
- showAndAssertAllActionsMenu();
+ showAndAssertAllActionsMenu(true);
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
+ }
+
+ @Test
+ public void testCloseMenu_MoveModeFollowedByMoveMode() {
+ showAndAssertMoveMenu(true);
+ showAndAssertMoveMenu(false);
+
+ closeMenuAndAssertMenuClosed(true);
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
- showAndAssertMoveMenu();
- showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu(true);
+ showAndAssertAllActionsMenu(false);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
}
@Test
public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu(true);
+ showAndAssertMoveMenu(false);
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
- public void testExitMoveMode_NoMenuMode() {
- mTvPipMenuController.onExitMoveMode();
- assertMenuIsOpen(false);
- verify(mMockDelegate, never()).onMenuClosed();
+ public void testCloseMenu_AllActionsModeFollowedByAllActionsMode() {
+ showAndAssertAllActionsMenu(true);
+ showAndAssertAllActionsMenu(false);
+
+ closeMenuAndAssertMenuClosed(true);
+ verify(mMockDelegate, never()).onInMoveModeChanged();
}
@Test
- public void testExitMoveMode_MoveMode() {
- showAndAssertMoveMenu();
+ public void testExitMenuMode_NoMenuMode() {
+ mTvPipMenuController.onExitCurrentMenuMode();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ verify(mMockDelegate, never()).onInMoveModeChanged();
+ }
- mTvPipMenuController.onExitMoveMode();
+ @Test
+ public void testExitMenuMode_MoveMode() {
+ showAndAssertMoveMenu(true);
+
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
assertMenuClosed();
verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
- public void testExitMoveMode_AllActionsMode() {
- showAndAssertAllActionsMenu();
+ public void testExitMenuMode_AllActionsMode() {
+ showAndAssertAllActionsMenu(true);
- mTvPipMenuController.onExitMoveMode();
- assertMenuIsInAllActionsMode();
-
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
}
@Test
- public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ public void testExitMenuMode_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu(true);
+ showAndAssertMoveMenu(false);
- mTvPipMenuController.onExitMoveMode();
- assertMenuIsInAllActionsMode();
- verify(mMockDelegate, times(2)).onInMoveModeChanged();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
- verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
- }
-
- @Test
- public void testOnBackPress_NoMenuMode() {
- mTvPipMenuController.onBackPress();
- assertMenuIsOpen(false);
- verify(mMockDelegate, never()).onMenuClosed();
- }
-
- @Test
- public void testOnBackPress_MoveMode() {
- showAndAssertMoveMenu();
-
- pressBackAndAssertMenuClosed();
- verify(mMockDelegate, times(2)).onInMoveModeChanged();
- }
-
- @Test
- public void testOnBackPress_AllActionsMode() {
- showAndAssertAllActionsMenu();
-
- pressBackAndAssertMenuClosed();
- }
-
- @Test
- public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
- showAndAssertMoveMenu();
- showAndAssertAllActionsMenu();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ assertSwitchedToAllActionsMode(2);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- pressBackAndAssertMenuClosed();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
}
@Test
- public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ public void testExitMenuMode_AllActionsModeFollowedByAllActionsMode() {
+ showAndAssertAllActionsMenu(true);
+ showAndAssertAllActionsMenu(false);
- mTvPipMenuController.onBackPress();
- assertMenuIsInAllActionsMode();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
+ verify(mMockDelegate, never()).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMenuMode_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu(true);
+
+ showAndAssertAllActionsMenu(false);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
- verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
- pressBackAndAssertMenuClosed();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ @Test
+ public void testExitMenuMode_MoveModeFollowedByMoveMode() {
+ showAndAssertMoveMenu(true);
+ showAndAssertMoveMenu(false);
+
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testOnPipMovement_NoMenuMode() {
- assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ moveAndAssertMoveSuccessful(false);
}
@Test
public void testOnPipMovement_MoveMode() {
- showAndAssertMoveMenu();
- assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
- verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+ showAndAssertMoveMenu(true);
+ moveAndAssertMoveSuccessful(true);
}
@Test
public void testOnPipMovement_AllActionsMode() {
- showAndAssertAllActionsMenu();
- assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ showAndAssertAllActionsMenu(true);
+ moveAndAssertMoveSuccessful(false);
}
@Test
- public void testOnPipWindowFocusChanged_NoMenuMode() {
- mTvPipMenuController.onPipWindowFocusChanged(false);
- assertMenuIsOpen(false);
- }
+ public void testUnexpectedFocusChanges() {
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(1);
- @Test
- public void testOnPipWindowFocusChanged_MoveMode() {
- showAndAssertMoveMenu();
- mTvPipMenuController.onPipWindowFocusChanged(false);
+ mFocusChangeListener.onWindowFocusChanged(false);
assertMenuClosed();
+
+ showAndAssertMoveMenu(true);
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed(2);
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
- public void testOnPipWindowFocusChanged_AllActionsMode() {
- showAndAssertAllActionsMenu();
- mTvPipMenuController.onPipWindowFocusChanged(false);
- assertMenuClosed();
- }
-
- private void showAndAssertMoveMenu() {
- mTvPipMenuController.showMovementMenu();
- assertMenuIsInMoveMode();
- verify(mMockDelegate).onInMoveModeChanged();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
- verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_MOVE_MENU));
- }
-
- private void showAndAssertAllActionsMenu() {
+ public void testAsyncScenario_AllActionsModeRequestFollowedByAsyncMoveModeRequest() {
mTvPipMenuController.showMenu();
- assertMenuIsInAllActionsMode();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
- verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ // Artificially delaying the focus change update and adding a move request to simulate an
+ // async problematic situation.
+ mTvPipMenuController.showMovementMenu();
+ // The first focus change update arrives
+ mFocusChangeListener.onWindowFocusChanged(true);
+
+ // We expect that the TvPipMenuController will directly switch to the "pending" menu mode
+ // - MODE_MOVE_MENU, because no change of focus is needed.
+ assertSwitchedToMoveMode();
}
- private void closeMenuAndAssertMenuClosed() {
+ @Test
+ public void testAsyncScenario_MoveModeRequestFollowedByAsyncAllActionsModeRequest() {
+ mTvPipMenuController.showMovementMenu();
+ mTvPipMenuController.showMenu();
+
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(1);
+ verify(mMockDelegate, never()).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testAsyncScenario_DropObsoleteIntermediateModeSwitchRequests() {
+ mTvPipMenuController.showMovementMenu();
mTvPipMenuController.closeMenu();
+
+ // Focus change from showMovementMenu() call.
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToMoveMode();
+ verify(mMockDelegate).onInMoveModeChanged();
+
+ // Focus change from closeMenu() call.
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ // Unexpected focus gain should open MODE_ALL_ACTIONS_MENU.
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(1);
+
+ mTvPipMenuController.closeMenu();
+ mTvPipMenuController.showMovementMenu();
+
+ assertSwitchedToMoveMode(2);
+
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed(2);
+
+ // Closing the menu resets the default menu mode, so the next focus gain opens the menu in
+ // the default mode - MODE_ALL_ACTIONS_MENU.
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(2);
+ verify(mMockDelegate, times(4)).onInMoveModeChanged();
+
+ }
+
+ private void showAndAssertMoveMenu(boolean focusChange) {
+ mTvPipMenuController.showMovementMenu();
+ if (focusChange) {
+ mFocusChangeListener.onWindowFocusChanged(true);
+ }
+ assertSwitchedToMoveMode();
+ }
+
+ private void assertSwitchedToMoveMode() {
+ assertSwitchedToMoveMode(1);
+ }
+
+ private void assertSwitchedToMoveMode(int times) {
+ assertMenuIsInMoveMode();
+ verify(mMockDelegate, times(2 * times - 1)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU));
+ verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU));
+ }
+
+ private void showAndAssertAllActionsMenu(boolean focusChange) {
+ showAndAssertAllActionsMenu(focusChange, 1);
+ }
+
+ private void showAndAssertAllActionsMenu(boolean focusChange, int times) {
+ mTvPipMenuController.showMenu();
+ if (focusChange) {
+ mFocusChangeListener.onWindowFocusChanged(true);
+ }
+
+ assertSwitchedToAllActionsMode(times);
+ }
+
+ private void assertSwitchedToAllActionsMode(int times) {
+ assertMenuIsInAllActionsMode();
+ verify(mMockTvPipMenuView, times(times))
+ .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ verify(mMockTvPipBackgroundView, times(times))
+ .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ }
+
+ private void closeMenuAndAssertMenuClosed(boolean focusChange) {
+ mTvPipMenuController.closeMenu();
+ if (focusChange) {
+ mFocusChangeListener.onWindowFocusChanged(false);
+ }
assertMenuClosed();
}
- private void pressBackAndAssertMenuClosed() {
- mTvPipMenuController.onBackPress();
- assertMenuClosed();
+ private void moveAndAssertMoveSuccessful(boolean expectedSuccess) {
+ mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE);
+ verify(mMockDelegate, times(expectedSuccess ? 1 : 0)).movePip(eq(TEST_MOVE_KEYCODE));
}
private void assertMenuClosed() {
+ assertMenuClosed(1);
+ }
+
+ private void assertMenuClosed(int times) {
assertMenuIsOpen(false);
- verify(mMockDelegate).onMenuClosed();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
- verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_NO_MENU));
+ verify(mMockDelegate, times(times)).onMenuClosed();
+ verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU));
+ verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU));
}
private void assertMenuIsOpen(boolean open) {
@@ -312,15 +424,10 @@
assertMenuIsOpen(true);
}
- private void assertPipMoveSuccessful(boolean expected, boolean actual) {
- assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
- + mTvPipMenuController.getMenuModeString(), expected == actual);
- }
-
private class TestTvPipMenuController extends TvPipMenuController {
TestTvPipMenuController() {
- super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+ super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMainHandler);
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index baa06f2..bbd65be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,6 +24,7 @@
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
import com.android.wm.shell.util.GroupedRecentTaskInfo
import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
@@ -123,6 +124,7 @@
assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2)
assertThat(recentTaskInfoParcel.splitBounds).isNotNull()
+ assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_50_50)
}
@Test
@@ -156,7 +158,7 @@
private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
val task1 = createTaskInfo(id = 1)
val task2 = createTaskInfo(id = 2)
- val splitBounds = SplitBounds(Rect(), Rect(), 1, 2)
+ val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_50_50)
return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 2c69522..10e9e11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -17,10 +17,12 @@
package com.android.wm.shell.recents;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -172,10 +174,10 @@
// Verify only one update if the split info is the same
SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
}
@@ -206,8 +208,10 @@
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
- SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
- SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ SplitBounds pair2Bounds =
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -235,8 +239,10 @@
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
- SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
- SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ SplitBounds pair2Bounds =
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -256,7 +262,7 @@
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto2Enabled()).thenReturn(true);
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -296,7 +302,7 @@
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto2Enabled()).thenReturn(false);
+ when(DesktopModeStatus.isEnabled()).thenReturn(false);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -333,7 +339,8 @@
setRawList(t1, t2, t3);
// Add a pair
- SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 3);
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
@@ -367,6 +374,37 @@
verify(mRecentTasksController).notifyRecentTasksChanged();
}
+ @Test
+ public void getNullSplitBoundsNonSplitTask() {
+ SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
+ assertNull("splitBounds should be null for non-split task", sb);
+ }
+
+ @Test
+ public void getNullSplitBoundsInvalidTask() {
+ SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(INVALID_TASK_ID);
+ assertNull("splitBounds should be null for invalid taskID", sb);
+ }
+
+ @Test
+ public void getSplitBoundsForSplitTask() {
+ SplitBounds pair1Bounds = mock(SplitBounds.class);
+ SplitBounds pair2Bounds = mock(SplitBounds.class);
+
+ mRecentTasksController.addSplitPair(1, 2, pair1Bounds);
+ mRecentTasksController.addSplitPair(4, 3, pair2Bounds);
+
+ SplitBounds splitBounds2 = mRecentTasksController.getSplitBoundsForTaskId(2);
+ SplitBounds splitBounds1 = mRecentTasksController.getSplitBoundsForTaskId(1);
+ assertEquals("Different splitBounds for same pair", splitBounds1, splitBounds2);
+ assertEquals(splitBounds1, pair1Bounds);
+
+ SplitBounds splitBounds3 = mRecentTasksController.getSplitBoundsForTaskId(3);
+ SplitBounds splitBounds4 = mRecentTasksController.getSplitBoundsForTaskId(4);
+ assertEquals("Different splitBounds for same pair", splitBounds3, splitBounds4);
+ assertEquals(splitBounds4, pair2Bounds);
+ }
+
/**
* Helper to create a task with a given task id.
*/
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index 50d02ae..b790aee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -1,5 +1,7 @@
package com.android.wm.shell.recents;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -44,21 +46,21 @@
@Test
public void testVerticalStacked() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
assertTrue(ssb.appsStackedVertically);
}
@Test
public void testHorizontalStacked() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
assertFalse(ssb.appsStackedVertically);
}
@Test
public void testHorizontalDividerBounds() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(0, dividerBounds.left);
assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top);
@@ -69,7 +71,7 @@
@Test
public void testVerticalDividerBounds() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
assertEquals(0, dividerBounds.top);
@@ -80,7 +82,7 @@
@Test
public void testEqualVerticalTaskPercent() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
}
@@ -88,7 +90,7 @@
@Test
public void testEqualHorizontalTaskPercent() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 568db91..855b7ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -218,8 +219,7 @@
}
@Test
- public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -235,13 +235,14 @@
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
- verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
+ verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -258,8 +259,8 @@
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
-
- verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
new file mode 100644
index 0000000..30847d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.splitscreen;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.split.SplitScreenUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/** Tests for {@link com.android.wm.shell.common.split.SplitScreenUtils} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitScreenUtilsTests extends ShellTestCase {
+
+ @Test
+ public void testIsLeftRightSplit() {
+ Configuration portraitTablet = new Configuration();
+ portraitTablet.smallestScreenWidthDp = 720;
+ portraitTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+ Configuration landscapeTablet = new Configuration();
+ landscapeTablet.smallestScreenWidthDp = 720;
+ landscapeTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+ Configuration portraitPhone = new Configuration();
+ portraitPhone.smallestScreenWidthDp = 420;
+ portraitPhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+ Configuration landscapePhone = new Configuration();
+ landscapePhone.smallestScreenWidthDp = 420;
+ landscapePhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+
+ // Allow L/R split in portrait = false
+ assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ landscapeTablet));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ landscapePhone));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ portraitTablet));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ portraitPhone));
+
+ // Allow L/R split in portrait = true, only affects large screens
+ assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ landscapeTablet));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ landscapePhone));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ portraitTablet));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ portraitPhone));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 5efd9ad..befc702 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -47,11 +47,8 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.os.IBinder;
-import android.os.RemoteException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -66,7 +63,6 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -77,6 +73,8 @@
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.TestRemoteTransition;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -205,7 +203,31 @@
// Make sure split-screen is now visible
assertTrue(mStageCoordinator.isSplitScreenVisible());
- assertTrue(testRemote.mCalled);
+ assertTrue(testRemote.isCalled());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testRemoteTransitionConsumed() {
+ // Omit side child change
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(TRANSIT_OPEN, mMainChild)
+ .build();
+ TestRemoteTransition testRemote = new TestRemoteTransition();
+
+ IBinder transition = mSplitScreenTransitions.startEnterTransition(
+ TRANSIT_OPEN, new WindowContainerTransaction(),
+ new RemoteTransition(testRemote, "Test"), mStageCoordinator,
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ mMainStage.onTaskAppeared(mMainChild, createMockSurface());
+ boolean accepted = mStageCoordinator.startAnimation(transition, info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(Transitions.TransitionFinishCallback.class));
+ assertTrue(accepted);
+
+ assertTrue(testRemote.isConsumed());
+
}
@Test
@@ -468,24 +490,4 @@
return out;
}
- class TestRemoteTransition extends IRemoteTransition.Stub {
- boolean mCalled = false;
- final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
-
- @Override
- public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction startTransaction,
- IRemoteTransitionFinishedCallback finishCallback)
- throws RemoteException {
- mCalled = true;
- finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
- }
-
- @Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
- }
- }
-
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index cc4db22..d819261 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -74,6 +74,7 @@
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -139,7 +140,7 @@
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
- when(mSplitLayout.isLandscape()).thenReturn(false);
+ when(mSplitLayout.isLeftRightSplit()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
@@ -373,7 +374,7 @@
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index a9082a6..012c4081 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -80,6 +80,7 @@
MockitoAnnotations.initMocks(this);
doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
+ doReturn(super.mContext.getResources()).when(mContext).getResources();
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
mMainExecutor));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 0088051..d7c4610 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,9 +40,12 @@
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
@@ -56,6 +59,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestHandler;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
@@ -88,6 +92,9 @@
SyncTransactionQueue mSyncQueue;
@Mock
Transitions mTransitions;
+ @Mock
+ Looper mViewLooper;
+ TestHandler mViewHandler;
SurfaceSession mSession;
SurfaceControl mLeash;
@@ -105,6 +112,8 @@
.build();
mContext = getContext();
+ doReturn(true).when(mViewLooper).isCurrentThread();
+ mViewHandler = spy(new TestHandler(mViewLooper));
mTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.token = mToken;
@@ -132,6 +141,7 @@
mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
mTaskViewTransitions, mSyncQueue));
mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setHandler(mViewHandler);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -646,4 +656,37 @@
assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
}
+
+ @Test
+ public void testOnTaskInfoChangedOnSameUiThread() {
+ mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+ verify(mViewHandler, never()).post(any());
+ }
+
+ @Test
+ public void testOnTaskInfoChangedOnDifferentUiThread() {
+ doReturn(false).when(mViewLooper).isCurrentThread();
+ mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+ verify(mViewHandler).post(any());
+ }
+
+ @Test
+ public void testSetResizeBgOnSameUiThread_expectUsesTransaction() {
+ SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+ mTaskView = spy(mTaskView);
+ mTaskView.setResizeBgColor(tx, Color.BLUE);
+ verify(mViewHandler, never()).post(any());
+ verify(mTaskView, never()).setResizeBackgroundColor(eq(Color.BLUE));
+ verify(mTaskView).setResizeBackgroundColor(eq(tx), eq(Color.BLUE));
+ }
+
+ @Test
+ public void testSetResizeBgOnDifferentUiThread_expectDoesNotUseTransaction() {
+ doReturn(false).when(mViewLooper).isCurrentThread();
+ SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+ mTaskView = spy(mTaskView);
+ mTaskView.setResizeBgColor(tx, Color.BLUE);
+ verify(mViewHandler).post(any());
+ verify(mTaskView).setResizeBackgroundColor(eq(Color.BLUE));
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 03ed18c..0504439 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -65,12 +65,6 @@
ActivityManager.RunningTaskInfo mTaskInfo;
@Mock
WindowContainerToken mToken;
- @Mock
- TaskViewTaskController mTaskViewTaskController2;
- @Mock
- ActivityManager.RunningTaskInfo mTaskInfo2;
- @Mock
- WindowContainerToken mToken2;
TaskViewTransitions mTaskViewTransitions;
@@ -86,16 +80,10 @@
mTaskInfo.token = mToken;
mTaskInfo.taskId = 314;
mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
- when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
-
- mTaskInfo2 = new ActivityManager.RunningTaskInfo();
- mTaskInfo2.token = mToken2;
- mTaskInfo2.taskId = 315;
- mTaskInfo2.taskDescription = mock(ActivityManager.TaskDescription.class);
- when(mTaskViewTaskController2.getTaskInfo()).thenReturn(mTaskInfo2);
mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+ when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
}
@Test
@@ -138,7 +126,7 @@
}
@Test
- public void testSetTaskBounds_taskVisibleWithPendingOpen_noTransaction() {
+ public void testSetTaskBounds_taskVisibleWithPending_noTransaction() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
@@ -154,43 +142,6 @@
}
@Test
- public void testSetTaskBounds_taskVisibleWithPendingChange_transition() {
- assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-
- mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
-
- // Consume the pending transition from visibility change
- TaskViewTransitions.PendingTransition pending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(pending).isNotNull();
- mTaskViewTransitions.startAnimation(pending.mClaimed,
- mock(TransitionInfo.class),
- new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(),
- mock(Transitions.TransitionFinishCallback.class));
- // Verify it was consumed
- TaskViewTransitions.PendingTransition checkPending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(checkPending).isNull();
-
- // Test that set bounds creates a new transition
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 100, 100));
- assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
- .isNotNull();
-
- // Test that set bounds again (with different bounds) creates another transition
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 300, 200));
- List<TaskViewTransitions.PendingTransition> pendingList =
- mTaskViewTransitions.findAllPending(mTaskViewTaskController)
- .stream()
- .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
- .toList();
- assertThat(pendingList.size()).isEqualTo(2);
- }
-
- @Test
public void testSetTaskBounds_sameBounds_noTransaction() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
@@ -217,16 +168,6 @@
mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
assertThat(pendingBounds).isNotNull();
- // Test that setting same bounds with in-flight transition doesn't cause another one
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 100, 100));
- List<TaskViewTransitions.PendingTransition> pendingList =
- mTaskViewTransitions.findAllPending(mTaskViewTaskController)
- .stream()
- .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
- .toList();
- assertThat(pendingList.size()).isEqualTo(1);
-
// Consume the pending bounds transaction
mTaskViewTransitions.startAnimation(pendingBounds.mClaimed,
mock(TransitionInfo.class),
@@ -246,42 +187,6 @@
assertThat(pendingBounds2).isNull();
}
-
- @Test
- public void testSetTaskBounds_taskVisibleWithDifferentTaskViewPendingChange_transition() {
- assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-
- mTaskViewTransitions.addTaskView(mTaskViewTaskController2);
-
- mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
-
- // Consume the pending transition from visibility change
- TaskViewTransitions.PendingTransition pending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(pending).isNotNull();
- mTaskViewTransitions.startAnimation(pending.mClaimed,
- mock(TransitionInfo.class),
- new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(),
- mock(Transitions.TransitionFinishCallback.class));
- // Verify it was consumed
- TaskViewTransitions.PendingTransition checkPending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(checkPending).isNull();
-
- // Set the second taskview as visible & check that it has a pending transition
- mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController2, true);
- TaskViewTransitions.PendingTransition pending2 =
- mTaskViewTransitions.findPending(mTaskViewTaskController2, TRANSIT_TO_FRONT);
- assertThat(pending2).isNotNull();
-
- // Test that set bounds on the first taskview will create a new transition
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 100, 100));
- assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
- .isNotNull();
- }
-
@Test
public void testSetTaskVisibility_taskRemoved_noNPE() {
mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
new file mode 100644
index 0000000..66efa02
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2023 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.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration.ActivityType;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionInfo.TransitionMode;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the home transition observer.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HomeTransitionObserverTest extends ShellTestCase {
+
+ private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
+ private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final ShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final DisplayController mDisplayController = mock(DisplayController.class);
+
+ private IHomeTransitionListener mListener;
+ private Transitions mTransition;
+ private HomeTransitionObserver mHomeTransitionObserver;
+
+ @Before
+ public void setUp() {
+ mListener = mock(IHomeTransitionListener.class);
+ when(mListener.asBinder()).thenReturn(mock(IBinder.class));
+
+ mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor);
+ mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
+ mOrganizer, mTransactionPool, mDisplayController, mMainExecutor,
+ mMainHandler, mAnimExecutor, mHomeTransitionObserver);
+ mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener);
+ }
+
+ @Test
+ public void testHomeActivityWithOpenModeNotifiesHomeIsVisible() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(1)).onHomeVisibilityChanged(true);
+ }
+
+ @Test
+ public void testHomeActivityWithCloseModeNotifiesHomeIsNotVisible() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_TO_BACK, true);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(1)).onHomeVisibilityChanged(false);
+ }
+
+ @Test
+ public void testNonHomeActivityDoesNotTriggerCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_UNDEFINED, TRANSIT_TO_BACK, true);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
+ }
+
+ @Test
+ public void testNonRunningHomeActivityDoesNotTriggerCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_UNDEFINED, TRANSIT_TO_BACK, false);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
+ }
+
+ @Test
+ public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ when(change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)).thenReturn(true);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_CHANGE, true);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(1)).onHomeVisibilityChanged(true);
+ }
+
+ /**
+ * Helper class to initialize variables for the rest.
+ */
+ private void setupTransitionInfo(ActivityManager.RunningTaskInfo taskInfo,
+ TransitionInfo.Change change,
+ @ActivityType int activityType,
+ @TransitionMode int mode,
+ boolean isRunning) {
+ when(taskInfo.getActivityType()).thenReturn(activityType);
+ when(change.getMode()).thenReturn(mode);
+ taskInfo.isRunning = isRunning;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b32e0d6..e22bf3d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -40,6 +40,8 @@
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -50,6 +52,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
@@ -84,7 +87,6 @@
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -92,13 +94,17 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -124,7 +130,7 @@
@RunWith(AndroidJUnit4.class)
public class ShellTransitionTests extends ShellTestCase {
- private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
+ private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
private final TransactionPool mTransactionPool = mock(TransactionPool.class);
private final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -144,8 +150,10 @@
ShellInit shellInit = mock(ShellInit.class);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
- verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ // One from Transitions, one from RootTaskDisplayAreaOrganizer
+ verify(shellInit).addInitCallback(any(), eq(t));
+ verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
}
@Test
@@ -154,7 +162,7 @@
ShellController shellController = mock(ShellController.class);
final Transitions t = new Transitions(mContext, shellInit, shellController,
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
@@ -285,6 +293,10 @@
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
}
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ }
};
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
@@ -427,6 +439,10 @@
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
}
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ }
};
TransitionFilter filter = new TransitionFilter();
@@ -473,6 +489,10 @@
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
}
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ }
};
final int transitType = TRANSIT_FIRST_CUSTOM + 1;
@@ -1059,9 +1079,10 @@
final Transitions transitions =
new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
- new RecentsTransitionHandler(shellInit, transitions, null);
+ new RecentsTransitionHandler(shellInit, transitions,
+ mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
@@ -1446,6 +1467,43 @@
assertEquals(0, mDefaultHandler.activeCount());
}
+ @Test
+ public void testCloseTransitAnimationWhenClosingChangesExists() {
+ Transitions transitions = createTestTransitions();
+ Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+ transitions.registerObserver(observer);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+ final TransitionAnimation transitionAnimation = new TransitionAnimation(mContext, false,
+ Transitions.TAG);
+ spyOn(transitionAnimation);
+
+ // Creating a transition by the app hooking the back key event to start the
+ // previous activity with FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ // flags in order to clear the top activity and bring the exist previous activity to front.
+ // Expects the activity transition should playing the close animation instead the initiated
+ // open animation made by startActivity.
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_CLOSE).addChange(TRANSIT_TO_FRONT).build();
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ final int type = getTransitionTypeFromInfo(info);
+ assertEquals(TRANSIT_CLOSE, type);
+
+ TransitionAnimationHelper.loadAttributeAnimation(type, info, info.getChanges().get(0), 0,
+ transitionAnimation, false);
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation), anyBoolean());
+
+ TransitionAnimationHelper.loadAttributeAnimation(type, info, info.getChanges().get(1), 0,
+ transitionAnimation, false);
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseEnterAnimation), anyBoolean());
+ }
+
class ChangeBuilder {
final TransitionInfo.Change mChange;
@@ -1606,7 +1664,7 @@
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
new file mode 100644
index 0000000..87330d2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.transition;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+/**
+ * {@link IRemoteTransition} for testing purposes.
+ * Stores info about
+ * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
+ * IRemoteTransitionFinishedCallback)} being called.
+ */
+public class TestRemoteTransition extends IRemoteTransition.Stub {
+ private boolean mCalled = false;
+ private boolean mConsumed = false;
+ final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
+
+ @Override
+ public void startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ IRemoteTransitionFinishedCallback finishCallback)
+ throws RemoteException {
+ mCalled = true;
+ finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ mConsumed = true;
+ }
+
+ /**
+ * Check whether this remote transition
+ * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
+ * IRemoteTransitionFinishedCallback)} is called
+ */
+ public boolean isCalled() {
+ return mCalled;
+ }
+
+ /**
+ * Check whether this remote transition's {@link #onTransitionConsumed(IBinder, boolean)}
+ * is called
+ */
+ public boolean isConsumed() {
+ return mConsumed;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
similarity index 98%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index a658375..8343858 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 1d94c9c..c5e229f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -17,6 +17,9 @@
package com.android.wm.shell.unfold;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH;
+import static android.view.WindowManager.TRANSIT_NONE;
import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +30,7 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.view.Display;
@@ -38,6 +42,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
@@ -87,10 +92,13 @@
}
@Test
- public void handleRequest_physicalDisplayChange_handlesTransition() {
+ public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
- Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+ Display.DEFAULT_DISPLAY)
+ .setPhysicalDisplayChanged(true)
+ .setStartAbsBounds(new Rect(0, 0, 100, 100))
+ .setEndAbsBounds(new Rect(0, 0, 200, 200));
TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
@@ -101,6 +109,23 @@
}
@Test
+ public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() {
+ ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+ TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+ Display.DEFAULT_DISPLAY)
+ .setPhysicalDisplayChanged(true)
+ .setStartAbsBounds(new Rect(0, 0, 200, 200))
+ .setEndAbsBounds(new Rect(0, 0, 100, 100));
+ TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+ triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+
+ WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+ requestInfo);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
@@ -132,6 +157,71 @@
}
@Test
+ public void startAnimation_sameTransitionAsHandleRequest_startsAnimation() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isTrue();
+ }
+
+ @Test
+ public void startAnimation_differentTransitionFromRequestWithUnfold_startsAnimation() {
+ mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ createUnfoldTransitionInfo(),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isTrue();
+ }
+
+ @Test
+ public void startAnimation_differentTransitionFromRequestWithResize_doesNotStartAnimation() {
+ mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ createDisplayResizeTransitionInfo(),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isFalse();
+ }
+
+ @Test
+ public void startAnimation_differentTransitionFromRequestWithoutUnfold_doesNotStart() {
+ mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ createNonUnfoldTransitionInfo(),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isFalse();
+ }
+
+ @Test
public void startAnimation_animationFinishes_finishesTheTransition() {
TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
@@ -207,14 +297,59 @@
verify(finishCallback).onTransitionFinished(any());
}
+ @Test
+ public void mergeAnimation_eatsDisplayOnlyTransitions() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+ TransitionFinishCallback mergeCallback = mock(TransitionFinishCallback.class);
+
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback);
+
+ // Offer a keyguard unlock transition - this should NOT merge
+ mUnfoldTransitionHandler.mergeAnimation(
+ new Binder(),
+ new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(),
+ mock(SurfaceControl.Transaction.class),
+ mTransition,
+ mergeCallback);
+ verify(finishCallback, never()).onTransitionFinished(any());
+
+ // Offer a CHANGE-only transition - this SHOULD merge (b/278064943)
+ mUnfoldTransitionHandler.mergeAnimation(
+ new Binder(),
+ new TransitionInfoBuilder(TRANSIT_CHANGE).build(),
+ mock(SurfaceControl.Transaction.class),
+ mTransition,
+ mergeCallback);
+ verify(mergeCallback).onTransitionFinished(any());
+
+ // We should never have finished the original transition.
+ verify(finishCallback, never()).onTransitionFinished(any());
+ }
+
private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
- Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+ Display.DEFAULT_DISPLAY)
+ .setPhysicalDisplayChanged(true)
+ .setStartAbsBounds(new Rect(0, 0, 100, 100))
+ .setEndAbsBounds(new Rect(0, 0, 200, 200));
return new TransitionRequestInfo(TRANSIT_CHANGE,
triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
}
+ private TransitionRequestInfo createNoneTransitionInfo() {
+ return new TransitionRequestInfo(TRANSIT_NONE,
+ /* triggerTask= */ null, /* remoteTransition= */ null,
+ /* displayChange= */ null, /* flags= */ 0);
+ }
+
private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
ShellUnfoldProgressProvider.UnfoldListener {
@@ -277,4 +412,29 @@
return false;
}
}
-}
\ No newline at end of file
+
+ private TransitionInfo createUnfoldTransitionInfo() {
+ TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
+ TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
+ change.setStartAbsBounds(new Rect(0, 0, 10, 10));
+ change.setEndAbsBounds(new Rect(0, 0, 100, 100));
+ change.setFlags(TransitionInfo.FLAG_IS_DISPLAY);
+ transitionInfo.addChange(change);
+ transitionInfo.setFlags(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH);
+ return transitionInfo;
+ }
+
+ private TransitionInfo createDisplayResizeTransitionInfo() {
+ TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
+ TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
+ change.setStartAbsBounds(new Rect(0, 0, 10, 10));
+ change.setEndAbsBounds(new Rect(0, 0, 100, 100));
+ change.setFlags(TransitionInfo.FLAG_IS_DISPLAY);
+ transitionInfo.addChange(change);
+ return transitionInfo;
+ }
+
+ private TransitionInfo createNonUnfoldTransitionInfo() {
+ return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
deleted file mode 100644
index 596d6dd..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * 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.windowdecor;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.hardware.input.InputManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.InputChannel;
-import android.view.InputMonitor;
-import android.view.SurfaceControl;
-import android.view.SurfaceView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
-import com.android.wm.shell.desktopmode.DesktopTasksController;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
-
-/** Tests of {@link DesktopModeWindowDecorViewModel} */
-@SmallTest
-public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
-
- private static final String TAG = "DesktopModeWindowDecorViewModelTests";
- private static final Rect STABLE_INSETS = new Rect(0, 100, 0, 0);
-
- @Mock private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
- @Mock private DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
-
- @Mock private Handler mMainHandler;
- @Mock private Choreographer mMainChoreographer;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
- @Mock private DisplayLayout mDisplayLayout;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private DesktopModeController mDesktopModeController;
- @Mock private DesktopTasksController mDesktopTasksController;
- @Mock private InputMonitor mInputMonitor;
- @Mock private InputManager mInputManager;
- @Mock private Transitions mTransitions;
- @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
- @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
- @Mock private SurfaceControl.Transaction mTransaction;
- @Mock private Display mDisplay;
- @Mock private ShellController mShellController;
- @Mock private ShellInit mShellInit;
- @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
- mDesktopModeKeyguardChangeListener;
- private final List<InputManager> mMockInputManagers = new ArrayList<>();
-
- private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
-
- @Before
- public void setUp() {
- mMockInputManagers.add(mInputManager);
-
- mDesktopModeWindowDecorViewModel =
- new DesktopModeWindowDecorViewModel(
- mContext,
- mMainHandler,
- mMainChoreographer,
- mShellInit,
- mTaskOrganizer,
- mDisplayController,
- mShellController,
- mSyncQueue,
- mTransitions,
- Optional.of(mDesktopModeController),
- Optional.of(mDesktopTasksController),
- mDesktopModeWindowDecorFactory,
- mMockInputMonitorFactory,
- mTransactionFactory,
- mDesktopModeKeyguardChangeListener
- );
-
- doReturn(mDesktopModeWindowDecoration)
- .when(mDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), any(), any(), any(), any(), any());
- doReturn(mTransaction).when(mTransactionFactory).get();
- doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt());
- doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets();
- doNothing().when(mShellController).addKeyguardChangeListener(any());
-
- when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
- // InputChannel cannot be mocked because it passes to InputEventReceiver.
- final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
- inputChannels[0].dispose();
- when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
-
- mDesktopModeWindowDecoration.mDisplay = mDisplay;
- doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
- }
-
- @Test
- public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- runOnMainThread(() -> {
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-
- mDesktopModeWindowDecorViewModel.onTaskOpening(
- taskInfo, surfaceControl, startT, finishT);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mDesktopModeWindowDecorViewModel.onTaskChanging(
- taskInfo, surfaceControl, startT, finishT);
- });
- verify(mDesktopModeWindowDecorFactory)
- .create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
- verify(mDesktopModeWindowDecoration).close();
- }
-
- @Test
- public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- runOnMainThread(() -> {
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
-
- mDesktopModeWindowDecorViewModel.onTaskChanging(
- taskInfo, surfaceControl, startT, finishT);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-
- mDesktopModeWindowDecorViewModel.onTaskChanging(
- taskInfo, surfaceControl, startT, finishT);
- });
- verify(mDesktopModeWindowDecorFactory, times(1))
- .create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
- }
-
- @Test
- public void testCreateAndDisposeEventReceiver() throws Exception {
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- runOnMainThread(() -> {
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-
- mDesktopModeWindowDecorViewModel.onTaskOpening(
- taskInfo, surfaceControl, startT, finishT);
-
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
- });
- verify(mMockInputMonitorFactory).create(any(), any());
- verify(mInputMonitor).dispose();
- }
-
- @Test
- public void testEventReceiversOnMultipleDisplays() throws Exception {
- runOnMainThread(() -> {
- SurfaceView surfaceView = new SurfaceView(mContext);
- final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
- final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
- "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
- /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
- DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
- try {
- int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
-
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
- final ActivityManager.RunningTaskInfo secondTaskInfo =
- createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
- final ActivityManager.RunningTaskInfo thirdTaskInfo =
- createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
-
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-
- mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT,
- finishT);
- mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
- startT, finishT);
- mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
- startT, finishT);
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
- } finally {
- secondaryDisplay.release();
- }
- });
- verify(mMockInputMonitorFactory, times(2)).create(any(), any());
- verify(mInputMonitor, times(1)).dispose();
- }
-
- @Test
- public void testCaptionIsNotCreatedWhenKeyguardIsVisible() throws Exception {
- doReturn(true).when(
- mDesktopModeKeyguardChangeListener).isKeyguardVisibleAndOccluded();
-
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN);
- taskInfo.isFocused = true;
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- runOnMainThread(() -> {
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-
- mDesktopModeWindowDecorViewModel.onTaskOpening(
- taskInfo, surfaceControl, startT, finishT);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mDesktopModeWindowDecorViewModel.onTaskChanging(
- taskInfo, surfaceControl, startT, finishT);
- });
- verify(mDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), any(), any(), any(), any(), any());
- }
-
- private void runOnMainThread(Runnable r) throws Exception {
- final Handler mainHandler = new Handler(Looper.getMainLooper());
- final CountDownLatch latch = new CountDownLatch(1);
- mainHandler.post(() -> {
- r.run();
- latch.countDown();
- });
- latch.await(1, TimeUnit.SECONDS);
- }
-
- private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
- int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
- ActivityManager.RunningTaskInfo taskInfo =
- new TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setVisible(true)
- .build();
- taskInfo.taskId = taskId;
- taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
- return taskInfo;
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
new file mode 100644
index 0000000..883c24e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -0,0 +1,453 @@
+/*
+ * 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.windowdecor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.Context
+import android.graphics.Rect
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.os.Handler
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Choreographer
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.InputChannel
+import android.view.InputMonitor
+import android.view.InsetsSource
+import android.view.InsetsState
+import android.view.SurfaceControl
+import android.view.SurfaceView
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.sysui.KeyguardChangeListener
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+import java.util.Optional
+import java.util.function.Supplier
+
+
+/** Tests of [DesktopModeWindowDecorViewModel] */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
+ @Mock private lateinit var mockDesktopModeWindowDecorFactory:
+ DesktopModeWindowDecoration.Factory
+ @Mock private lateinit var mockMainHandler: Handler
+ @Mock private lateinit var mockMainChoreographer: Choreographer
+ @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock private lateinit var displayInsetsController: DisplayInsetsController
+ @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
+ @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
+ @Mock private lateinit var mockInputMonitor: InputMonitor
+ @Mock private lateinit var mockTransitions: Transitions
+ @Mock private lateinit var mockInputMonitorFactory:
+ DesktopModeWindowDecorViewModel.InputMonitorFactory
+ @Mock private lateinit var mockShellController: ShellController
+ @Mock private lateinit var mockShellExecutor: ShellExecutor
+ @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
+ @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
+
+ private val transactionFactory = Supplier<SurfaceControl.Transaction> {
+ SurfaceControl.Transaction()
+ }
+
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
+
+ @Before
+ fun setUp() {
+ shellInit = ShellInit(mockShellExecutor)
+ desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
+ mContext,
+ mockMainHandler,
+ mockMainChoreographer,
+ shellInit,
+ mockShellCommandHandler,
+ mockTaskOrganizer,
+ mockDisplayController,
+ mockShellController,
+ displayInsetsController,
+ mockSyncQueue,
+ mockTransitions,
+ Optional.of(mockDesktopTasksController),
+ mockRecentsTransitionHandler,
+ mockDesktopModeWindowDecorFactory,
+ mockInputMonitorFactory,
+ transactionFactory,
+ mockRootTaskDisplayAreaOrganizer
+ )
+
+ whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
+
+ // InputChannel cannot be mocked because it passes to InputEventReceiver.
+ val inputChannels = InputChannel.openInputChannelPair(TAG)
+ inputChannels.first().dispose()
+ whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1])
+
+ shellInit.init()
+
+ val listenerCaptor =
+ argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+ verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
+ desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
+ }
+
+ @Test
+ fun testDeleteCaptionOnChangeTransitionWhenNecessary() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val taskSurface = SurfaceControl()
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task, taskSurface)
+
+ task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
+ task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
+ onTaskChanging(task, taskSurface)
+
+ verify(mockDesktopModeWindowDecorFactory).create(
+ mContext,
+ mockDisplayController,
+ mockTaskOrganizer,
+ task,
+ taskSurface,
+ mockMainHandler,
+ mockMainChoreographer,
+ mockSyncQueue,
+ mockRootTaskDisplayAreaOrganizer
+ )
+ verify(decoration).close()
+ }
+
+ @Test
+ fun testCreateCaptionOnChangeTransitionWhenNecessary() {
+ val task = createTask(
+ windowingMode = WINDOWING_MODE_UNDEFINED,
+ activityType = ACTIVITY_TYPE_UNDEFINED
+ )
+ val taskSurface = SurfaceControl()
+ setUpMockDecorationForTask(task)
+
+ onTaskChanging(task, taskSurface)
+ verify(mockDesktopModeWindowDecorFactory, never()).create(
+ mContext,
+ mockDisplayController,
+ mockTaskOrganizer,
+ task,
+ taskSurface,
+ mockMainHandler,
+ mockMainChoreographer,
+ mockSyncQueue,
+ mockRootTaskDisplayAreaOrganizer
+ )
+
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM)
+ task.setActivityType(ACTIVITY_TYPE_STANDARD)
+ onTaskChanging(task, taskSurface)
+ verify(mockDesktopModeWindowDecorFactory, times(1)).create(
+ mContext,
+ mockDisplayController,
+ mockTaskOrganizer,
+ task,
+ taskSurface,
+ mockMainHandler,
+ mockMainChoreographer,
+ mockSyncQueue,
+ mockRootTaskDisplayAreaOrganizer
+ )
+ }
+
+ @Test
+ fun testCreateAndDisposeEventReceiver() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+ verify(mockInputMonitorFactory).create(any(), any())
+ verify(mockInputMonitor).dispose()
+ }
+
+ @Test
+ fun testEventReceiversOnMultipleDisplays() {
+ val secondaryDisplay = createVirtualDisplay() ?: return
+ val secondaryDisplayId = secondaryDisplay.display.displayId
+ val task = createTask(displayId = DEFAULT_DISPLAY, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask = createTask(
+ displayId = secondaryDisplayId,
+ windowingMode = WINDOWING_MODE_FREEFORM
+ )
+ val thirdTask = createTask(
+ displayId = secondaryDisplayId,
+ windowingMode = WINDOWING_MODE_FREEFORM
+ )
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTask)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+ secondaryDisplay.release()
+
+ verify(mockInputMonitorFactory, times(2)).create(any(), any())
+ verify(mockInputMonitor, times(1)).dispose()
+ }
+
+ @Test
+ fun testCaptionIsNotCreatedWhenKeyguardIsVisible() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ val keyguardListenerCaptor = argumentCaptor<KeyguardChangeListener>()
+ verify(mockShellController).addKeyguardChangeListener(keyguardListenerCaptor.capture())
+
+ keyguardListenerCaptor.firstValue.onKeyguardVisibilityChanged(
+ true /* visible */,
+ true /* occluded */,
+ false /* animatingDismiss */
+ )
+ onTaskOpening(task)
+
+ task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
+ task.setWindowingMode(ACTIVITY_TYPE_UNDEFINED)
+ onTaskChanging(task)
+
+ verify(mockDesktopModeWindowDecorFactory, never())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testRelayoutBlockedDuringRecentsTransition() {
+ val recentsCaptor = argumentCaptor<RecentsTransitionStateListener>()
+ verify(mockRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture())
+
+ val transition = mock(IBinder::class.java)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+
+ // Make sure a window decorations exists first by launching a freeform task.
+ onTaskOpening(task)
+ // Now call back when a Recents transition starts.
+ recentsCaptor.firstValue.onTransitionStarted(transition)
+
+ verify(decoration).incrementRelayoutBlock()
+ verify(decoration).addTransitionPausingRelayout(transition)
+ }
+
+ @Test
+ fun testRelayoutBlockedDuringKeyguardTransition() {
+ val transition = mock(IBinder::class.java)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ val transitionInfo = mock(TransitionInfo::class.java)
+ val transitionChange = mock(TransitionInfo.Change::class.java)
+ val taskInfo = mock(RunningTaskInfo()::class.java)
+
+ // Replicate a keyguard going away transition for a task
+ whenever(transitionInfo.getFlags())
+ .thenReturn(WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
+ whenever(transitionChange.getMode()).thenReturn(WindowManager.TRANSIT_TO_FRONT)
+ whenever(transitionChange.getTaskInfo()).thenReturn(taskInfo)
+
+ // Make sure a window decorations exists first by launching a freeform task.
+ onTaskOpening(task)
+ // OnTransition ready is called when a keyguard going away transition happens
+ desktopModeWindowDecorViewModel
+ .onTransitionReady(transition, transitionInfo, transitionChange)
+
+ verify(decoration).incrementRelayoutBlock()
+ verify(decoration).addTransitionPausingRelayout(transition)
+ }
+ @Test
+ fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add status bar insets source
+ val insetsState = InsetsState()
+ val statusBarInsetsSourceId = 0
+ val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+ statusBarInsetsSource.isVisible = false
+ insetsState.addSource(statusBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout occurs when status bar inset visibility changes
+ verify(decoration, times(1)).relayout(task)
+ }
+
+ @Test
+ fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add navigation bar insets source
+ val insetsState = InsetsState()
+ val navigationBarInsetsSourceId = 1
+ val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
+ navigationBarInsetsSource.isVisible = false
+ insetsState.addSource(navigationBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout does not occur when non-status bar inset changes visibility
+ verify(decoration, never()).relayout(task)
+ }
+
+ @Test
+ fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add status bar insets source
+ val insetsState = InsetsState()
+ val statusBarInsetsSourceId = 0
+ val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+ statusBarInsetsSource.isVisible = false
+ insetsState.addSource(statusBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout runs only once when status bar inset visibility changes.
+ verify(decoration, times(1)).relayout(task)
+ }
+
+ private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
+ desktopModeWindowDecorViewModel.onTaskOpening(
+ task,
+ leash,
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction()
+ )
+ }
+
+ private fun onTaskChanging(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
+ desktopModeWindowDecorViewModel.onTaskChanging(
+ task,
+ leash,
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction()
+ )
+ }
+
+ private fun createTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ @WindowConfiguration.WindowingMode windowingMode: Int,
+ activityType: Int = ACTIVITY_TYPE_STANDARD,
+ focused: Boolean = true
+ ): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setWindowingMode(windowingMode)
+ .setVisible(true)
+ .setActivityType(activityType)
+ .build().apply {
+ isFocused = focused
+ }
+ }
+
+ private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
+ val decoration = mock(DesktopModeWindowDecoration::class.java)
+ whenever(mockDesktopModeWindowDecorFactory.create(
+ any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ ).thenReturn(decoration)
+ decoration.mTaskInfo = task
+ whenever(decoration.isFocused).thenReturn(task.isFocused)
+ return decoration
+ }
+
+ private fun setUpMockDecorationsForTasks(vararg tasks: RunningTaskInfo) {
+ tasks.forEach { setUpMockDecorationForTask(it) }
+ }
+
+ private fun createVirtualDisplay(): VirtualDisplay? {
+ val surfaceView = SurfaceView(mContext)
+ val dm = mContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ return dm.createVirtualDisplay(
+ "testEventReceiversOnMultipleDisplays",
+ /*width=*/ 400,
+ /*height=*/ 400,
+ /*densityDpi=*/320,
+ surfaceView.holder.surface,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ )
+ }
+
+ private fun RunningTaskInfo.setWindowingMode(@WindowConfiguration.WindowingMode mode: Int) {
+ configuration.windowConfiguration.windowingMode = mode
+ }
+
+ private fun RunningTaskInfo.setActivityType(type: Int) {
+ configuration.windowConfiguration.activityType = type
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeWindowDecorViewModelTests"
+ private val STABLE_INSETS = Rect(0, 100, 0, 0)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
new file mode 100644
index 0000000..18fcdd0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.windowdecor;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.function.Supplier;
+
+/**
+ * Tests for {@link DesktopModeWindowDecoration}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DesktopModeWindowDecorationTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DesktopModeWindowDecorationTests extends ShellTestCase {
+ @Mock
+ private DisplayController mMockDisplayController;
+ @Mock
+ private ShellTaskOrganizer mMockShellTaskOrganizer;
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private Choreographer mMockChoreographer;
+ @Mock
+ private SyncTransactionQueue mMockSyncQueue;
+ @Mock
+ private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
+ @Mock
+ private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier;
+ @Mock
+ private SurfaceControl.Transaction mMockTransaction;
+ @Mock
+ private SurfaceControl mMockSurfaceControl;
+ @Mock
+ private SurfaceControlViewHost mMockSurfaceControlViewHost;
+ @Mock
+ private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
+
+ private final Configuration mConfiguration = new Configuration();
+
+ @Before
+ public void setUp() {
+ doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory).create(
+ any(), any(), any());
+ doReturn(mMockTransaction).when(mMockTransactionSupplier).get();
+ }
+
+ @Test
+ public void testMenusClosedWhenTaskIsInvisible() {
+ doReturn(mMockTransaction).when(mMockTransaction).hide(any());
+
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(false /* visible */);
+ final DesktopModeWindowDecoration spyWindowDecor =
+ spy(createWindowDecoration(taskInfo));
+
+ spyWindowDecor.relayout(taskInfo);
+
+ // Menus should close if open before the task being invisible causes relayout to return.
+ verify(spyWindowDecor).closeHandleMenu();
+ verify(spyWindowDecor).closeMaximizeMenu();
+
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
+ mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
+ mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+ SurfaceControl.Builder::new, mMockTransactionSupplier,
+ WindowContainerTransaction::new, SurfaceControl::new,
+ mMockSurfaceControlViewHostFactory);
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(visible)
+ .build();
+ taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
+ "DesktopModeWindowDecorationTests");
+ taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
+ "DesktopModeWindowDecorationTests");
+ return taskInfo;
+
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index de46b31..5c0e04a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -76,7 +76,7 @@
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
- configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 6f0599a..add78b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -6,6 +6,9 @@
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
@@ -24,6 +27,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
@@ -76,7 +80,15 @@
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
@@ -88,7 +100,8 @@
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
- configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -623,7 +636,7 @@
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -641,11 +654,83 @@
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
}
})
}
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ })
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = Surface.ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ })
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -664,11 +749,17 @@
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 3465ddd..a70ebf1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,9 @@
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerToken
@@ -30,6 +33,7 @@
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -93,10 +97,17 @@
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
-
mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
@@ -104,7 +115,8 @@
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
- configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -343,7 +355,7 @@
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -361,11 +373,79 @@
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
}
})
}
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Drag back to starting bounds.
+ performDrag(STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -378,11 +458,17 @@
private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 7fc1c99..8e42f74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -17,12 +17,19 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,9 +43,11 @@
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
@@ -47,6 +56,7 @@
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
import android.view.Display;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
@@ -58,10 +68,12 @@
import androidx.test.filters.SmallTest;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import org.junit.Before;
@@ -88,6 +100,7 @@
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
private static final int CORNER_RADIUS = 20;
+ private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -108,17 +121,19 @@
private WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
private SurfaceSyncGroup mMockSurfaceSyncGroup;
+ @Mock
+ private SurfaceControl mMockTaskSurface;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
+ private final InsetsState mInsetsState = new InsetsState();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
+ private Configuration mWindowConfiguration = new Configuration();
private int mCaptionMenuWidthId;
- private int mCaptionMenuShadowRadiusId;
- private int mCaptionMenuCornerRadiusId;
@Before
public void setUp() {
@@ -129,8 +144,6 @@
mRelayoutParams.mLayoutResId = 0;
mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
- mCaptionMenuShadowRadiusId = R.dimen.test_caption_menu_shadow_radius;
- mCaptionMenuCornerRadiusId = R.dimen.test_caption_menu_corner_radius;
mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
mRelayoutParams.mCornerRadius = CORNER_RADIUS;
@@ -138,6 +151,11 @@
.create(any(), any(), any());
when(mMockSurfaceControlViewHost.getRootSurfaceControl())
.thenReturn(mMockRootSurfaceControl);
+ when(mMockView.findViewById(anyInt())).thenReturn(mMockView);
+
+ // Add status bar inset so that WindowDecoration does not think task is in immersive mode
+ mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
+ doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
}
@Test
@@ -173,8 +191,7 @@
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -183,7 +200,7 @@
verify(captionContainerSurfaceBuilder, never()).build();
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
- verify(mMockSurfaceControlFinishT).hide(taskSurface);
+ verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
assertNull(mRelayoutResult.mRootView);
}
@@ -203,12 +220,8 @@
createMockSurfaceControlBuilder(captionContainerSurface);
mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
- final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
- new ActivityManager.TaskDescription.Builder()
- .setBackgroundColor(Color.YELLOW);
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY)
- .setTaskDescriptionBuilder(taskDescriptionBuilder)
.setBounds(TASK_BOUNDS)
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
@@ -217,12 +230,11 @@
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
@@ -250,16 +262,15 @@
}
verify(mMockSurfaceControlFinishT)
- .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
+ TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setWindowCrop(taskSurface, 300, 100);
- verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
+ .setWindowCrop(mMockTaskSurface, 300, 100);
+ verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+ verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
- .show(taskSurface);
- verify(mMockSurfaceControlStartT)
- .setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
- verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
+ .show(mMockTaskSurface);
+ verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
assertEquals(300, mRelayoutResult.mWidth);
assertEquals(100, mRelayoutResult.mHeight);
@@ -296,9 +307,9 @@
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -335,8 +346,7 @@
.setVisible(true)
.build();
- final TestWindowDecoration windowDecor =
- createWindowDecoration(taskInfo, new SurfaceControl());
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
// It shouldn't show the window decoration when it can't obtain the display instance.
@@ -394,8 +404,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
@@ -414,16 +423,6 @@
final int height = WindowDecoration.loadDimensionPixelSize(
windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
- final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
- windowDecor.mDecorWindowContext.getResources(),
- mCaptionMenuShadowRadiusId);
- verify(mMockSurfaceControlAddWindowT)
- .setShadowRadius(additionalWindowSurface, shadowRadius);
- final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
- windowDecor.mDecorWindowContext.getResources(),
- mCaptionMenuCornerRadiusId);
- verify(mMockSurfaceControlAddWindowT)
- .setCornerRadius(additionalWindowSurface, cornerRadius);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
.create(any(), eq(defaultDisplay), any());
@@ -464,8 +463,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -505,23 +503,154 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
}
- private TestWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+ @Test
+ public void testRelayout_fluidResizeEnabled_freeformTask_setTaskSurfaceColor() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).strictness(
+ LENIENT).startMocking();
+ when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
+
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ taskInfo.isFocused = true;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
+
+ mockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testInsetsAddedWhenCaptionIsVisible() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
+ .isVisible());
+ assertTrue(mInsetsState.sourceSize() == 1);
+ assertTrue(mInsetsState.sourceAt(0).getType() == statusBars());
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()), any());
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()), any());
+ }
+
+ @Test
+ public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).strictness(LENIENT).startMocking();
+ when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
+
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build();
+ taskInfo.isFocused = true;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
+
+ mockitoSession.finishMocking();
+ }
+
+
+ @Test
+ public void testInsetsRemovedWhenCaptionIsHidden() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()));
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()));
+ }
+
+ private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface,
+ taskInfo, mMockTaskSurface, mWindowConfiguration,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
- () -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
+ () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
+ mMockSurfaceControlViewHostFactory);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -556,13 +685,16 @@
TestWindowDecoration(Context context, DisplayController displayController,
ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
+ Configuration windowConfiguration,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
- surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
- windowContainerTransactionSupplier, surfaceControlViewHostFactory);
+ windowConfiguration, surfaceControlBuilderSupplier,
+ surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
+ surfaceControlSupplier, surfaceControlViewHostFactory);
}
@Override
@@ -583,13 +715,11 @@
int y = mRelayoutParams.mCaptionY;
int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
- int shadowRadius = loadDimensionPixelSize(resources, mCaptionMenuShadowRadiusId);
- int cornerRadius = loadDimensionPixelSize(resources, mCaptionMenuCornerRadiusId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
- addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
+ addWindow(R.layout.desktop_mode_window_decor_handle_menu, name,
mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
- width, height, shadowRadius, cornerRadius);
+ width, height);
return additionalWindow;
}
}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 4fb80ac..2f28363 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -63,15 +63,21 @@
"AssetsProvider.cpp",
"AttributeResolution.cpp",
"BigBuffer.cpp",
+ "BigBufferStream.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
+ "FileStream.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
"Locale.cpp",
"LocaleData.cpp",
"misc.cpp",
+ "NinePatch.cpp",
"ObbFile.cpp",
"PosixUtils.cpp",
+ "Png.cpp",
+ "PngChunkFilter.cpp",
+ "PngCrunch.cpp",
"ResourceTimer.cpp",
"ResourceTypes.cpp",
"ResourceUtils.cpp",
@@ -84,7 +90,10 @@
],
export_include_dirs: ["include"],
export_shared_lib_headers: ["libz"],
- static_libs: ["libincfs-utils"],
+ static_libs: [
+ "libincfs-utils",
+ "libpng",
+ ],
whole_static_libs: [
"libandroidfw_pathutils",
"libincfs-utils",
@@ -198,9 +207,11 @@
"tests/ConfigDescription_test.cpp",
"tests/ConfigLocale_test.cpp",
"tests/DynamicRefTable_test.cpp",
+ "tests/FileStream_test.cpp",
"tests/Idmap_test.cpp",
"tests/LoadedArsc_test.cpp",
"tests/Locale_test.cpp",
+ "tests/NinePatch_test.cpp",
"tests/ResourceTimer_test.cpp",
"tests/ResourceUtils_test.cpp",
"tests/ResTable_test.cpp",
@@ -257,6 +268,7 @@
"tests/AssetManager2_bench.cpp",
"tests/AttributeResolution_bench.cpp",
"tests/CursorWindow_bench.cpp",
+ "tests/Generic_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp
index 32d2c5b..7eedfdb 100644
--- a/libs/androidfw/ApkParsing.cpp
+++ b/libs/androidfw/ApkParsing.cpp
@@ -56,6 +56,11 @@
return nullptr;
}
+ // Make sure file starts with 'lib/' prefix.
+ if (strncmp(fileName, APK_LIB.data(), APK_LIB_LEN) != 0) {
+ return nullptr;
+ }
+
// Make sure there aren't subdirectories by checking if the next / after lib/ is the last slash
if (memchr(fileName + APK_LIB_LEN, '/', fileNameLen - APK_LIB_LEN) != lastSlash) {
return nullptr;
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5e04bfe..8748dab 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -97,12 +97,17 @@
Res_value value;
};
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
- : configuration_(configuration) {
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
+ configurations_.push_back(configuration);
+
// Don't invalidate caches here as there's nothing cached yet.
SetApkAssets(apk_assets, false);
}
+AssetManager2::AssetManager2() {
+ configurations_.resize(1);
+}
+
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
BuildDynamicRefTable(apk_assets);
RebuildFilterList();
@@ -266,7 +271,7 @@
auto op = StartOperation();
std::string list;
- for (size_t i = 0; i < apk_assets_.size(); ++i) {
+ for (size_t i = 0, s = apk_assets_.size(); i < s; ++i) {
const auto& assets = GetApkAssets(i);
base::StringAppendF(&list, "%s,", assets ? assets->GetDebugName().c_str() : "nullptr");
}
@@ -359,7 +364,7 @@
std::string* out) const {
auto op = StartOperation();
uint8_t package_id = 0U;
- for (size_t i = 0; i != apk_assets_.size(); ++i) {
+ for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) {
const auto& assets = GetApkAssets(i);
if (!assets) {
continue;
@@ -418,7 +423,7 @@
bool AssetManager2::ContainsAllocatedTable() const {
auto op = StartOperation();
- for (size_t i = 0; i != apk_assets_.size(); ++i) {
+ for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) {
const auto& assets = GetApkAssets(i);
if (assets && assets->IsTableAllocated()) {
return true;
@@ -427,9 +432,16 @@
return false;
}
-void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
- const int diff = configuration_.diff(configuration);
- configuration_ = configuration;
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
+ int diff = 0;
+ if (configurations_.size() != configurations.size()) {
+ diff = -1;
+ } else {
+ for (int i = 0; i < configurations_.size(); i++) {
+ diff |= configurations_[i].diff(configurations[i]);
+ }
+ }
+ configurations_ = std::move(configurations);
if (diff) {
RebuildFilterList();
@@ -591,7 +603,7 @@
std::unique_ptr<Asset> asset = assets->GetAssetsProvider()->Open(filename, mode);
if (asset) {
if (out_cookie != nullptr) {
- *out_cookie = i;
+ *out_cookie = i - 1;
}
return asset;
}
@@ -626,16 +638,6 @@
auto op = StartOperation();
- // Might use this if density_override != 0.
- ResTable_config density_override_config;
-
- // Select our configuration or generate a density override configuration.
- const ResTable_config* desired_config = &configuration_;
- if (density_override != 0 && density_override != configuration_.density) {
- density_override_config = configuration_;
- density_override_config.density = density_override;
- desired_config = &density_override_config;
- }
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
@@ -654,119 +656,160 @@
}
const PackageGroup& package_group = package_groups_[package_idx];
- auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
- stop_at_first_match, ignore_configuration);
- if (UNLIKELY(!result.has_value())) {
- return base::unexpected(result.error());
- }
+ std::optional<FindEntryResult> final_result;
+ bool final_has_locale = false;
+ bool final_overlaid = false;
+ for (auto & config : configurations_) {
+ // Might use this if density_override != 0.
+ ResTable_config density_override_config;
- bool overlaid = false;
- if (!stop_at_first_match && !ignore_configuration) {
- const auto& assets = GetApkAssets(result->cookie);
- if (!assets) {
- ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
- return base::unexpected(std::nullopt);
+ // Select our configuration or generate a density override configuration.
+ const ResTable_config* desired_config = &config;
+ if (density_override != 0 && density_override != config.density) {
+ density_override_config = config;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
}
- if (!assets->IsLoader()) {
- for (const auto& id_map : package_group.overlays_) {
- auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
- if (!overlay_entry) {
- // No id map entry exists for this target resource.
- continue;
- }
- if (overlay_entry.IsInlineValue()) {
- // The target resource is overlaid by an inline value not represented by a resource.
- ConfigDescription best_frro_config;
- Res_value best_frro_value;
- bool frro_found = false;
- for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
- if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
- && config.match(*desired_config)) {
- frro_found = true;
- best_frro_config = config;
- best_frro_value = value;
- }
- }
- if (!frro_found) {
+
+ auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+ stop_at_first_match, ignore_configuration);
+ if (UNLIKELY(!result.has_value())) {
+ return base::unexpected(result.error());
+ }
+ bool overlaid = false;
+ if (!stop_at_first_match && !ignore_configuration) {
+ const auto& assets = GetApkAssets(result->cookie);
+ if (!assets) {
+ ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+ return base::unexpected(std::nullopt);
+ }
+ if (!assets->IsLoader()) {
+ for (const auto& id_map : package_group.overlays_) {
+ auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+ if (!overlay_entry) {
+ // No id map entry exists for this target resource.
continue;
}
- result->entry = best_frro_value;
+ if (overlay_entry.IsInlineValue()) {
+ // The target resource is overlaid by an inline value not represented by a resource.
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
+ }
+ }
+ if (!frro_found) {
+ continue;
+ }
+ result->entry = best_frro_value;
+ result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+ result->cookie = id_map.cookie;
+
+ if (UNLIKELY(logging_enabled)) {
+ last_resolution_.steps.push_back(Resolution::Step{
+ Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+ if (auto path = assets->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
+ overlaid = true;
+ }
+ continue;
+ }
+
+ auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+ false /* stop_at_first_match */,
+ false /* ignore_configuration */);
+ if (UNLIKELY(IsIOError(overlay_result))) {
+ return base::unexpected(overlay_result.error());
+ }
+ if (!overlay_result.has_value()) {
+ continue;
+ }
+
+ if (!overlay_result->config.isBetterThan(result->config, desired_config)
+ && overlay_result->config.compare(result->config) != 0) {
+ // The configuration of the entry for the overlay must be equal to or better than the
+ // target configuration to be chosen as the better value.
+ continue;
+ }
+
+ result->cookie = overlay_result->cookie;
+ result->entry = overlay_result->entry;
+ result->config = overlay_result->config;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
- result->cookie = id_map.cookie;
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, String8(), result->cookie});
- if (auto path = assets->GetPath()) {
- const std::string overlay_path = path->data();
- if (IsFabricatedOverlay(overlay_path)) {
- // FRRO don't have package name so we use the creating package here.
- String8 frro_name = String8("FRRO");
- // Get the first part of it since the expected one should be like
- // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
- // under /data/resource-cache/.
- const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
- const size_t end = name.find('-');
- if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
- frro_name.append(base::StringPrintf(" created by %s",
- name.substr(0 /* pos */,
- end).c_str()).c_str());
- }
- last_resolution_.best_package_name = frro_name;
- } else {
- last_resolution_.best_package_name = result->package_name->c_str();
- }
- }
+ Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+ overlay_result->config.toString()});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
overlaid = true;
}
- continue;
- }
-
- auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
- false /* stop_at_first_match */,
- false /* ignore_configuration */);
- if (UNLIKELY(IsIOError(overlay_result))) {
- return base::unexpected(overlay_result.error());
- }
- if (!overlay_result.has_value()) {
- continue;
- }
-
- if (!overlay_result->config.isBetterThan(result->config, desired_config)
- && overlay_result->config.compare(result->config) != 0) {
- // The configuration of the entry for the overlay must be equal to or better than the target
- // configuration to be chosen as the better value.
- continue;
- }
-
- result->cookie = overlay_result->cookie;
- result->entry = overlay_result->entry;
- result->config = overlay_result->config;
- result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-
- if (UNLIKELY(logging_enabled)) {
- last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(),
- overlay_result->cookie});
- last_resolution_.best_package_name =
- overlay_result->package_name->c_str();
- overlaid = true;
}
}
}
+
+ bool has_locale = false;
+ if (result->config.locale == 0) {
+ if (default_locale_ != 0) {
+ ResTable_config conf;
+ conf.locale = default_locale_;
+ // Since we know conf has a locale and only a locale, match will tell us if that locale
+ // matches
+ has_locale = conf.match(config);
+ }
+ } else {
+ has_locale = true;
+ }
+
+ // if we don't have a result yet
+ if (!final_result ||
+ // or this config is better before the locale than the existing result
+ result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
+ // or the existing config isn't better before locale and this one specifies a locale
+ // whereas the existing one doesn't
+ (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
+ && has_locale && !final_has_locale)) {
+ final_result = result.value();
+ final_overlaid = overlaid;
+ final_has_locale = has_locale;
+ }
}
if (UNLIKELY(logging_enabled)) {
- last_resolution_.cookie = result->cookie;
- last_resolution_.type_string_ref = result->type_string_ref;
- last_resolution_.entry_string_ref = result->entry_string_ref;
- last_resolution_.best_config_name = result->config.toString();
- if (!overlaid) {
- last_resolution_.best_package_name = result->package_name->c_str();
+ last_resolution_.cookie = final_result->cookie;
+ last_resolution_.type_string_ref = final_result->type_string_ref;
+ last_resolution_.entry_string_ref = final_result->entry_string_ref;
+ last_resolution_.best_config_name = final_result->config.toString();
+ if (!final_overlaid) {
+ last_resolution_.best_package_name = final_result->package_name->c_str();
}
}
- return result;
+ return *final_result;
}
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -784,8 +827,10 @@
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
- const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
-
+ const bool use_filtered = !ignore_configuration && std::find_if(
+ configurations_.begin(), configurations_.end(),
+ [&desired_config](auto& value) { return &desired_config == &value; })
+ != configurations_.end();
const size_t package_count = package_group.packages_.size();
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -818,9 +863,12 @@
// We can skip calling ResTable_config::match() if the caller does not care for the
// configuration to match or if we're using the list of types that have already had their
- // configuration matched.
+ // configuration matched. The exception to this is when the user has multiple locales set
+ // because the filtered list will then have values from multiple locales and we will need to
+ // call match() to make sure the current entry matches the config we are currently checking.
const ResTable_config& this_config = type_entry->config;
- if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+ if (!((use_filtered && (configurations_.size() == 1))
+ || ignore_configuration || this_config.match(desired_config))) {
continue;
}
@@ -834,8 +882,7 @@
} else {
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
- this_config.toString(),
- cookie});
+ cookie, this_config.toString()});
}
continue;
}
@@ -851,8 +898,7 @@
if (!offset.has_value()) {
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
- this_config.toString(),
- cookie});
+ cookie, this_config.toString()});
}
continue;
}
@@ -865,8 +911,7 @@
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(Resolution::Step{resolution_type,
- this_config.toString(),
- cookie});
+ cookie, this_config.toString()});
}
// Any configuration will suffice, so break.
@@ -895,11 +940,11 @@
.entry = *entry,
.config = *best_config,
.type_flags = type_flags,
+ .dynamic_ref_table = package_group.dynamic_ref_table.get(),
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
(*best_entry_verified)->key()),
- .dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
@@ -943,27 +988,40 @@
}
std::stringstream log_stream;
- log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
- "\tFor config - %s", resid, resource_name_string.c_str(),
- configuration_.toString().c_str());
-
+ if (configurations_.size() == 1) {
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+ "\tFor config - %s", resid, resource_name_string.c_str(),
+ configurations_[0].toString().c_str());
+ } else {
+ ResTable_config conf = configurations_[0];
+ conf.clearLocale();
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
+ resid, resource_name_string.c_str(), conf.toString().c_str());
+ char str[40];
+ str[0] = '\0';
+ for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
+ iter->getBcp47Locale(str);
+ log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
+ }
+ }
for (const Resolution::Step& step : last_resolution_.steps) {
- const static std::unordered_map<Resolution::Step::Type, const char*> kStepStrings = {
- {Resolution::Step::Type::INITIAL, "Found initial"},
- {Resolution::Step::Type::BETTER_MATCH, "Found better"},
- {Resolution::Step::Type::OVERLAID, "Overlaid"},
- {Resolution::Step::Type::OVERLAID_INLINE, "Overlaid inline"},
- {Resolution::Step::Type::SKIPPED, "Skipped"},
- {Resolution::Step::Type::NO_ENTRY, "No entry"}
+ constexpr static std::array kStepStrings = {
+ "Found initial",
+ "Found better",
+ "Overlaid",
+ "Overlaid inline",
+ "Skipped",
+ "No entry"
};
- const auto prefix = kStepStrings.find(step.type);
- if (prefix == kStepStrings.end()) {
+ if (step.type < Resolution::Step::Type::INITIAL
+ || step.type > Resolution::Step::Type::NO_ENTRY) {
continue;
}
+ const auto prefix = kStepStrings[int(step.type) - int(Resolution::Step::Type::INITIAL)];
const auto& assets = GetApkAssets(step.cookie);
- log_stream << "\n\t" << prefix->second << ": "
- << (assets ? assets->GetDebugName() : "<null>") << " #" << step.cookie;
+ log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>")
+ << " #" << step.cookie;
if (!step.config_name.empty()) {
log_stream << " - " << step.config_name;
}
@@ -1100,16 +1158,19 @@
}
}
-const std::vector<uint32_t> AssetManager2::GetBagResIdStack(uint32_t resid) const {
- auto cached_iter = cached_bag_resid_stacks_.find(resid);
- if (cached_iter != cached_bag_resid_stacks_.end()) {
- return cached_iter->second;
+base::expected<const std::vector<uint32_t>*, NullOrIOError> AssetManager2::GetBagResIdStack(
+ uint32_t resid) const {
+ auto it = cached_bag_resid_stacks_.find(resid);
+ if (it != cached_bag_resid_stacks_.end()) {
+ return &it->second;
+ }
+ std::vector<uint32_t> stacks;
+ if (auto maybe_bag = GetBag(resid, stacks); UNLIKELY(IsIOError(maybe_bag))) {
+ return base::unexpected(maybe_bag.error());
}
- std::vector<uint32_t> found_resids;
- GetBag(resid, found_resids);
- cached_bag_resid_stacks_.emplace(resid, found_resids);
- return found_resids;
+ it = cached_bag_resid_stacks_.emplace(resid, std::move(stacks)).first;
+ return &it->second;
}
base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag(
@@ -1126,9 +1187,15 @@
}
base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(uint32_t resid) const {
- std::vector<uint32_t> found_resids;
- const auto bag = GetBag(resid, found_resids);
- cached_bag_resid_stacks_.emplace(resid, std::move(found_resids));
+ auto resid_stacks_it = cached_bag_resid_stacks_.find(resid);
+ if (resid_stacks_it == cached_bag_resid_stacks_.end()) {
+ resid_stacks_it = cached_bag_resid_stacks_.emplace(resid, std::vector<uint32_t>{}).first;
+ }
+ const auto bag = GetBag(resid, resid_stacks_it->second);
+ if (UNLIKELY(IsIOError(bag))) {
+ cached_bag_resid_stacks_.erase(resid_stacks_it);
+ return base::unexpected(bag.error());
+ }
return bag;
}
@@ -1426,11 +1493,14 @@
package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
- if (type_entry.config.match(configuration_)) {
- if (!group) {
- group = &package.filtered_configs_.editItemAt(type_id - 1);
+ for (auto & config : configurations_) {
+ if (type_entry.config.match(config)) {
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
+ }
+ group->type_entries.push_back(&type_entry);
+ break;
}
- group->type_entries.push_back(&type_entry);
}
}
});
@@ -1441,25 +1511,40 @@
}
void AssetManager2::InvalidateCaches(uint32_t diff) {
- cached_bag_resid_stacks_.clear();
+ cached_resolved_values_.clear();
if (diff == 0xffffffffu) {
// Everything must go.
cached_bags_.clear();
+ cached_bag_resid_stacks_.clear();
return;
}
// Be more conservative with what gets purged. Only if the bag has other possible
// variations with respect to what changed (diff) should we remove it.
- for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
- if (diff & iter->second->type_spec_flags) {
- iter = cached_bags_.erase(iter);
+ for (auto stack_it = cached_bag_resid_stacks_.begin();
+ stack_it != cached_bag_resid_stacks_.end();) {
+ const auto it = cached_bags_.find(stack_it->first);
+ if (it == cached_bags_.end()) {
+ stack_it = cached_bag_resid_stacks_.erase(stack_it);
+ } else if ((diff & it->second->type_spec_flags) != 0) {
+ cached_bags_.erase(it);
+ stack_it = cached_bag_resid_stacks_.erase(stack_it);
} else {
- ++iter;
+ ++stack_it; // Keep the item in both caches.
}
}
- cached_resolved_values_.clear();
+ // Need to ensure that both bag caches are consistent, as we populate them in the same function.
+ // Iterate over the cached bags to erase the items without the corresponding resid_stack cache
+ // items.
+ for (auto it = cached_bags_.begin(); it != cached_bags_.end();) {
+ if ((diff & it->second->type_spec_flags) != 0) {
+ it = cached_bags_.erase(it);
+ } else {
+ ++it;
+ }
+ }
}
uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const {
diff --git a/libs/androidfw/BigBufferStream.cpp b/libs/androidfw/BigBufferStream.cpp
new file mode 100644
index 0000000..f18199c
--- /dev/null
+++ b/libs/androidfw/BigBufferStream.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "androidfw/BigBufferStream.h"
+
+#include <algorithm>
+
+namespace android {
+
+//
+// BigBufferInputStream
+//
+
+bool BigBufferInputStream::Next(const void** data, size_t* size) {
+ if (iter_ == buffer_->end()) {
+ return false;
+ }
+
+ if (offset_ == iter_->size) {
+ ++iter_;
+ if (iter_ == buffer_->end()) {
+ return false;
+ }
+ offset_ = 0;
+ }
+
+ *data = iter_->buffer.get() + offset_;
+ *size = iter_->size - offset_;
+ bytes_read_ += iter_->size - offset_;
+ offset_ = iter_->size;
+ return true;
+}
+
+void BigBufferInputStream::BackUp(size_t count) {
+ if (count > offset_) {
+ bytes_read_ -= offset_;
+ offset_ = 0;
+ } else {
+ offset_ -= count;
+ bytes_read_ -= count;
+ }
+}
+
+bool BigBufferInputStream::CanRewind() const {
+ return true;
+}
+
+bool BigBufferInputStream::Rewind() {
+ iter_ = buffer_->begin();
+ offset_ = 0;
+ bytes_read_ = 0;
+ return true;
+}
+
+size_t BigBufferInputStream::ByteCount() const {
+ return bytes_read_;
+}
+
+bool BigBufferInputStream::HadError() const {
+ return false;
+}
+
+size_t BigBufferInputStream::TotalSize() const {
+ return buffer_->size();
+}
+
+bool BigBufferInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
+ if (byte_count == 0) {
+ return true;
+ }
+ if (offset < 0) {
+ return false;
+ }
+ if (offset > std::numeric_limits<off64_t>::max() - byte_count) {
+ return false;
+ }
+ if (offset + byte_count > buffer_->size()) {
+ return false;
+ }
+ auto p = reinterpret_cast<uint8_t*>(data);
+ for (auto iter = buffer_->begin(); iter != buffer_->end() && byte_count > 0; ++iter) {
+ if (offset < iter->size) {
+ size_t to_read = std::min(byte_count, (size_t)(iter->size - offset));
+ memcpy(p, iter->buffer.get() + offset, to_read);
+ byte_count -= to_read;
+ p += to_read;
+ offset = 0;
+ } else {
+ offset -= iter->size;
+ }
+ }
+ return byte_count == 0;
+}
+
+//
+// BigBufferOutputStream
+//
+
+bool BigBufferOutputStream::Next(void** data, size_t* size) {
+ *data = buffer_->NextBlock(size);
+ return true;
+}
+
+void BigBufferOutputStream::BackUp(size_t count) {
+ buffer_->BackUp(count);
+}
+
+size_t BigBufferOutputStream::ByteCount() const {
+ return buffer_->size();
+}
+
+bool BigBufferOutputStream::HadError() const {
+ return false;
+}
+
+} // namespace android
diff --git a/libs/androidfw/FileStream.cpp b/libs/androidfw/FileStream.cpp
new file mode 100644
index 0000000..e898949
--- /dev/null
+++ b/libs/androidfw/FileStream.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "androidfw/FileStream.h"
+
+#include <errno.h> // for errno
+#include <fcntl.h> // for O_RDONLY
+#include <unistd.h> // for read
+
+#include "android-base/errors.h"
+#include "android-base/file.h" // for O_BINARY
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/utf8.h"
+
+#if defined(_WIN32)
+// This is only needed for O_CLOEXEC.
+#include <windows.h>
+#define O_CLOEXEC O_NOINHERIT
+#endif
+
+using ::android::base::SystemErrorCodeToString;
+using ::android::base::unique_fd;
+
+namespace android {
+
+FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
+ : should_close_(true), buffer_capacity_(buffer_capacity) {
+ int mode = O_RDONLY | O_CLOEXEC | O_BINARY;
+ fd_ = TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode));
+ if (fd_ == -1) {
+ error_ = SystemErrorCodeToString(errno);
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
+ : fd_(fd), should_close_(true), buffer_capacity_(buffer_capacity) {
+ if (fd_ < 0) {
+ error_ = "Bad File Descriptor";
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+FileInputStream::FileInputStream(android::base::borrowed_fd fd, size_t buffer_capacity)
+ : fd_(fd.get()), should_close_(false), buffer_capacity_(buffer_capacity) {
+
+ if (fd_ < 0) {
+ error_ = "Bad File Descriptor";
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+
+bool FileInputStream::Next(const void** data, size_t* size) {
+ if (HadError()) {
+ return false;
+ }
+
+ // Deal with any remaining bytes after BackUp was called.
+ if (buffer_offset_ != buffer_size_) {
+ *data = buffer_.get() + buffer_offset_;
+ *size = buffer_size_ - buffer_offset_;
+ total_byte_count_ += buffer_size_ - buffer_offset_;
+ buffer_offset_ = buffer_size_;
+ return true;
+ }
+
+ ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
+ if (n < 0) {
+ error_ = SystemErrorCodeToString(errno);
+ if (fd_ != -1) {
+ if (should_close_) {
+ close(fd_);
+ }
+ fd_ = -1;
+ }
+ buffer_.reset();
+ return false;
+ }
+
+ buffer_size_ = static_cast<size_t>(n);
+ buffer_offset_ = buffer_size_;
+ total_byte_count_ += buffer_size_;
+
+ *data = buffer_.get();
+ *size = buffer_size_;
+ return buffer_size_ != 0u;
+}
+
+void FileInputStream::BackUp(size_t count) {
+ if (count > buffer_offset_) {
+ count = buffer_offset_;
+ }
+ buffer_offset_ -= count;
+ total_byte_count_ -= count;
+}
+
+size_t FileInputStream::ByteCount() const {
+ return total_byte_count_;
+}
+
+bool FileInputStream::HadError() const {
+ return fd_ == -1;
+}
+
+std::string FileInputStream::GetError() const {
+ return error_;
+}
+
+bool FileInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
+ return base::ReadFullyAtOffset(fd_, data, byte_count, offset);
+}
+
+FileOutputStream::FileOutputStream(const std::string& path, size_t buffer_capacity)
+ : buffer_capacity_(buffer_capacity) {
+ int mode = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY;
+ owned_fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode, 0666)));
+ fd_ = owned_fd_.get();
+ if (fd_ < 0) {
+ error_ = SystemErrorCodeToString(errno);
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+FileOutputStream::FileOutputStream(unique_fd fd, size_t buffer_capacity)
+ : FileOutputStream(fd.get(), buffer_capacity) {
+ owned_fd_ = std::move(fd);
+}
+
+FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
+ : fd_(fd), buffer_capacity_(buffer_capacity) {
+ if (fd_ < 0) {
+ error_ = "Bad File Descriptor";
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+FileOutputStream::~FileOutputStream() {
+ // Flush the buffer.
+ Flush();
+}
+
+bool FileOutputStream::Next(void** data, size_t* size) {
+ if (HadError()) {
+ return false;
+ }
+
+ if (buffer_offset_ == buffer_capacity_) {
+ if (!FlushImpl()) {
+ return false;
+ }
+ }
+
+ const size_t buffer_size = buffer_capacity_ - buffer_offset_;
+ *data = buffer_.get() + buffer_offset_;
+ *size = buffer_size;
+ total_byte_count_ += buffer_size;
+ buffer_offset_ = buffer_capacity_;
+ return true;
+}
+
+void FileOutputStream::BackUp(size_t count) {
+ if (count > buffer_offset_) {
+ count = buffer_offset_;
+ }
+ buffer_offset_ -= count;
+ total_byte_count_ -= count;
+}
+
+size_t FileOutputStream::ByteCount() const {
+ return total_byte_count_;
+}
+
+bool FileOutputStream::Flush() {
+ if (!HadError()) {
+ return FlushImpl();
+ }
+ return false;
+}
+
+bool FileOutputStream::FlushImpl() {
+ ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
+ if (n < 0) {
+ error_ = SystemErrorCodeToString(errno);
+ owned_fd_.reset();
+ fd_ = -1;
+ buffer_.reset();
+ return false;
+ }
+
+ buffer_offset_ = 0u;
+ return true;
+}
+
+bool FileOutputStream::HadError() const {
+ return fd_ == -1;
+}
+
+std::string FileOutputStream::GetError() const {
+ return error_;
+}
+
+} // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 8983574..5f98b8f 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -294,14 +294,14 @@
dtohl(header->version), kIdmapCurrentVersion);
return {};
}
+ std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
+ if (!target_path) {
+ return {};
+ }
std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path");
if (!overlay_path) {
return {};
}
- std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
- if (!target_path) {
- return {};
- }
if (!ReadString(&data_ptr, &data_size, "target name") ||
!ReadString(&data_ptr, &data_size, "debug info")) {
return {};
@@ -364,7 +364,7 @@
return std::unique_ptr<LoadedIdmap>(
new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
target_inline_entries, target_inline_entry_values, configurations,
- overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
+ overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}
bool LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index fbfae5e..c9d5e07 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -494,6 +494,8 @@
util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
&loaded_package->package_name_);
+ const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0;
+
// A map of TypeSpec builders, each associated with an type index.
// We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
// contiguous block of memory that holds all the Types together with the TypeSpec.
@@ -502,6 +504,9 @@
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
+ if (only_overlayable && child_chunk.type() != RES_TABLE_OVERLAYABLE_TYPE) {
+ continue;
+ }
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE: {
const auto pool_address = child_chunk.header<ResChunk_header>();
@@ -655,6 +660,9 @@
<< name_to_actor_it->first << "'.";
return {};
}
+ if (only_overlayable) {
+ break;
+ }
// Iterate over the overlayable policy chunks contained within the overlayable chunk data
ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -800,14 +808,21 @@
global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
}
+ const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0;
+
const size_t package_count = dtohl(header->packageCount);
size_t packages_seen = 0;
- packages_.reserve(package_count);
+ if (!only_overlayable) {
+ packages_.reserve(package_count);
+ }
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
+ if (only_overlayable && child_chunk.type() != RES_TABLE_PACKAGE_TYPE) {
+ continue;
+ }
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE:
// Only use the first string pool. Ignore others.
@@ -837,6 +852,10 @@
return false;
}
packages_.push_back(std::move(loaded_package));
+ if (only_overlayable) {
+ // Overlayable is always in the first package, no need to process anything else.
+ return true;
+ }
} break;
default:
diff --git a/libs/androidfw/NinePatch.cpp b/libs/androidfw/NinePatch.cpp
new file mode 100644
index 0000000..1fdbebf
--- /dev/null
+++ b/libs/androidfw/NinePatch.cpp
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "androidfw/Image.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+using android::StringPiece;
+
+namespace android {
+
+// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
+constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
+constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
+constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
+
+constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
+constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
+
+/**
+ * Returns the alpha value encoded in the 0xAARRGBB encoded pixel.
+ */
+static uint32_t get_alpha(uint32_t color);
+
+/**
+ * Determines whether a color on an ImageLine is valid.
+ * A 9patch image may use a transparent color as neutral,
+ * or a fully opaque white color as neutral, based on the
+ * pixel color at (0,0) of the image. One or the other is fine,
+ * but we need to ensure consistency throughout the image.
+ */
+class ColorValidator {
+ public:
+ virtual ~ColorValidator() = default;
+
+ /**
+ * Returns true if the color specified is a neutral color
+ * (no padding, stretching, or optical bounds).
+ */
+ virtual bool IsNeutralColor(uint32_t color) const = 0;
+
+ /**
+ * Returns true if the color is either a neutral color
+ * or one denoting padding, stretching, or optical bounds.
+ */
+ bool IsValidColor(uint32_t color) const {
+ switch (color) {
+ case kPrimaryColor:
+ case kSecondaryColor:
+ return true;
+ }
+ return IsNeutralColor(color);
+ }
+};
+
+// Walks an ImageLine and records Ranges of primary and secondary colors.
+// The primary color is black and is used to denote a padding or stretching
+// range,
+// depending on which border we're iterating over.
+// The secondary color is red and is used to denote optical bounds.
+//
+// An ImageLine is a templated-interface that would look something like this if
+// it
+// were polymorphic:
+//
+// class ImageLine {
+// public:
+// virtual int32_t GetLength() const = 0;
+// virtual uint32_t GetColor(int32_t idx) const = 0;
+// };
+//
+template <typename ImageLine>
+static bool FillRanges(const ImageLine* image_line, const ColorValidator* color_validator,
+ std::vector<Range>* primary_ranges, std::vector<Range>* secondary_ranges,
+ std::string* out_err) {
+ const int32_t length = image_line->GetLength();
+
+ uint32_t last_color = 0xffffffffu;
+ for (int32_t idx = 1; idx < length - 1; idx++) {
+ const uint32_t color = image_line->GetColor(idx);
+ if (!color_validator->IsValidColor(color)) {
+ *out_err = "found an invalid color";
+ return false;
+ }
+
+ if (color != last_color) {
+ // We are ending a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (last_color == kPrimaryColor) {
+ primary_ranges->back().end = idx - 1;
+ } else if (last_color == kSecondaryColor) {
+ secondary_ranges->back().end = idx - 1;
+ }
+
+ // We are starting a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (color == kPrimaryColor) {
+ primary_ranges->push_back(Range(idx - 1, length - 2));
+ } else if (color == kSecondaryColor) {
+ secondary_ranges->push_back(Range(idx - 1, length - 2));
+ }
+ last_color = color;
+ }
+ }
+ return true;
+}
+
+/**
+ * Iterates over a row in an image. Implements the templated ImageLine
+ * interface.
+ */
+class HorizontalImageLine {
+ public:
+ explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
+ }
+
+ inline int32_t GetLength() const {
+ return length_;
+ }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
+};
+
+/**
+ * Iterates over a column in an image. Implements the templated ImageLine
+ * interface.
+ */
+class VerticalImageLine {
+ public:
+ explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
+ }
+
+ inline int32_t GetLength() const {
+ return length_;
+ }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
+};
+
+class DiagonalImageLine {
+ public:
+ explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t xstep,
+ int32_t ystep, int32_t length)
+ : rows_(rows),
+ xoffset_(xoffset),
+ yoffset_(yoffset),
+ xstep_(xstep),
+ ystep_(ystep),
+ length_(length) {
+ }
+
+ inline int32_t GetLength() const {
+ return length_;
+ }
+
+ inline uint32_t GetColor(int32_t idx) const {
+ return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + ((idx + xoffset_) * xstep_) * 4);
+ }
+
+ private:
+ uint8_t** rows_;
+ int32_t xoffset_, yoffset_, xstep_, ystep_, length_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
+};
+
+class TransparentNeutralColorValidator : public ColorValidator {
+ public:
+ bool IsNeutralColor(uint32_t color) const override {
+ return get_alpha(color) == 0;
+ }
+};
+
+class WhiteNeutralColorValidator : public ColorValidator {
+ public:
+ bool IsNeutralColor(uint32_t color) const override {
+ return color == kColorOpaqueWhite;
+ }
+};
+
+inline static uint32_t get_alpha(uint32_t color) {
+ return (color & 0xff000000u) >> 24;
+}
+
+static bool PopulateBounds(const std::vector<Range>& padding,
+ const std::vector<Range>& layout_bounds,
+ const std::vector<Range>& stretch_regions, const int32_t length,
+ int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
+ int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
+ if (padding.size() > 1) {
+ std::stringstream err_stream;
+ err_stream << "too many padding sections on " << edge_name << " border";
+ *out_err = err_stream.str();
+ return false;
+ }
+
+ *padding_start = 0;
+ *padding_end = 0;
+ if (!padding.empty()) {
+ const Range& range = padding.front();
+ *padding_start = range.start;
+ *padding_end = length - range.end;
+ } else if (!stretch_regions.empty()) {
+ // No padding was defined. Compute the padding from the first and last
+ // stretch regions.
+ *padding_start = stretch_regions.front().start;
+ *padding_end = length - stretch_regions.back().end;
+ }
+
+ if (layout_bounds.size() > 2) {
+ std::stringstream err_stream;
+ err_stream << "too many layout bounds sections on " << edge_name << " border";
+ *out_err = err_stream.str();
+ return false;
+ }
+
+ *layout_start = 0;
+ *layout_end = 0;
+ if (layout_bounds.size() >= 1) {
+ const Range& range = layout_bounds.front();
+ // If there is only one layout bound segment, it might not start at 0, but
+ // then it should
+ // end at length.
+ if (range.start != 0 && range.end != length) {
+ std::stringstream err_stream;
+ err_stream << "layout bounds on " << edge_name << " border must start at edge";
+ *out_err = err_stream.str();
+ return false;
+ }
+ *layout_start = range.end;
+
+ if (layout_bounds.size() >= 2) {
+ const Range& range = layout_bounds.back();
+ if (range.end != length) {
+ std::stringstream err_stream;
+ err_stream << "layout bounds on " << edge_name << " border must start at edge";
+ *out_err = err_stream.str();
+ return false;
+ }
+ *layout_end = length - range.start;
+ }
+ }
+ return true;
+}
+
+static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, int32_t length) {
+ if (stretch_regions.size() == 0) {
+ return 0;
+ }
+
+ const bool start_is_fixed = stretch_regions.front().start != 0;
+ const bool end_is_fixed = stretch_regions.back().end != length;
+ int32_t modifier = 0;
+ if (start_is_fixed && end_is_fixed) {
+ modifier = 1;
+ } else if (!start_is_fixed && !end_is_fixed) {
+ modifier = -1;
+ }
+ return static_cast<int32_t>(stretch_regions.size()) * 2 + modifier;
+}
+
+static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
+ // Sample the first pixel to compare against.
+ const uint32_t expected_color = NinePatch::PackRGBA(rows[region.top] + region.left * 4);
+ for (int32_t y = region.top; y < region.bottom; y++) {
+ const uint8_t* row = rows[y];
+ for (int32_t x = region.left; x < region.right; x++) {
+ const uint32_t color = NinePatch::PackRGBA(row + x * 4);
+ if (get_alpha(color) == 0) {
+ // The color is transparent.
+ // If the expectedColor is not transparent, NO_COLOR.
+ if (get_alpha(expected_color) != 0) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ } else if (color != expected_color) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ }
+ }
+
+ if (get_alpha(expected_color) == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return expected_color;
+}
+
+// Fills out_colors with each 9-patch section's color. If the whole section is
+// transparent,
+// it gets the special TRANSPARENT color. If the whole section is the same
+// color, it is assigned
+// that color. Otherwise it gets the special NO_COLOR color.
+//
+// Note that the rows contain the 9-patch 1px border, and the indices in the
+// stretch regions are
+// already offset to exclude the border. This means that each time the rows are
+// accessed,
+// the indices must be offset by 1.
+//
+// width and height also include the 9-patch 1px border.
+static void CalculateRegionColors(uint8_t** rows,
+ const std::vector<Range>& horizontal_stretch_regions,
+ const std::vector<Range>& vertical_stretch_regions,
+ const int32_t width, const int32_t height,
+ std::vector<uint32_t>* out_colors) {
+ int32_t next_top = 0;
+ Bounds bounds;
+ auto row_iter = vertical_stretch_regions.begin();
+ while (next_top != height) {
+ if (row_iter != vertical_stretch_regions.end()) {
+ if (next_top != row_iter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = next_top + 1;
+ bounds.bottom = row_iter->start + 1;
+ next_top = row_iter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = row_iter->start + 1;
+ bounds.bottom = row_iter->end + 1;
+ next_top = row_iter->end;
+ ++row_iter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = next_top + 1;
+ bounds.bottom = height + 1;
+ next_top = height;
+ }
+
+ int32_t next_left = 0;
+ auto col_iter = horizontal_stretch_regions.begin();
+ while (next_left != width) {
+ if (col_iter != horizontal_stretch_regions.end()) {
+ if (next_left != col_iter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = next_left + 1;
+ bounds.right = col_iter->start + 1;
+ next_left = col_iter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = col_iter->start + 1;
+ bounds.right = col_iter->end + 1;
+ next_left = col_iter->end;
+ ++col_iter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = next_left + 1;
+ bounds.right = width + 1;
+ next_left = width;
+ }
+ out_colors->push_back(GetRegionColor(rows, bounds));
+ }
+ }
+}
+
+// Calculates the insets of a row/column of pixels based on where the largest
+// alpha value begins
+// (on both sides).
+template <typename ImageLine>
+static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, int32_t* out_end) {
+ *out_start = 0;
+ *out_end = 0;
+
+ const int32_t length = image_line->GetLength();
+ if (length < 3) {
+ return;
+ }
+
+ // If the length is odd, we want both sides to process the center pixel,
+ // so we use two different midpoints (to account for < and <= in the different
+ // loops).
+ const int32_t mid2 = length / 2;
+ const int32_t mid1 = mid2 + (length % 2);
+
+ uint32_t max_alpha = 0;
+ for (int32_t i = 0; i < mid1 && max_alpha != 0xff; i++) {
+ uint32_t alpha = get_alpha(image_line->GetColor(i));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ *out_start = i;
+ }
+ }
+
+ max_alpha = 0;
+ for (int32_t i = length - 1; i >= mid2 && max_alpha != 0xff; i--) {
+ uint32_t alpha = get_alpha(image_line->GetColor(i));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ *out_end = length - (i + 1);
+ }
+ }
+ return;
+}
+
+template <typename ImageLine>
+static uint32_t FindMaxAlpha(const ImageLine* image_line) {
+ const int32_t length = image_line->GetLength();
+ uint32_t max_alpha = 0;
+ for (int32_t idx = 0; idx < length && max_alpha != 0xff; idx++) {
+ uint32_t alpha = get_alpha(image_line->GetColor(idx));
+ if (alpha > max_alpha) {
+ max_alpha = alpha;
+ }
+ }
+ return max_alpha;
+}
+
+// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
+uint32_t NinePatch::PackRGBA(const uint8_t* pixel) {
+ return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
+}
+
+std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, const int32_t width,
+ const int32_t height, std::string* out_err) {
+ if (width < 3 || height < 3) {
+ *out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
+ return {};
+ }
+
+ std::vector<Range> horizontal_padding;
+ std::vector<Range> horizontal_layout_bounds;
+ std::vector<Range> vertical_padding;
+ std::vector<Range> vertical_layout_bounds;
+ std::vector<Range> unexpected_ranges;
+ std::unique_ptr<ColorValidator> color_validator;
+
+ if (rows[0][3] == 0) {
+ color_validator = std::make_unique<TransparentNeutralColorValidator>();
+ } else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
+ color_validator = std::make_unique<WhiteNeutralColorValidator>();
+ } else {
+ *out_err = "top-left corner pixel must be either opaque white or transparent";
+ return {};
+ }
+
+ // Private constructor, can't use make_unique.
+ auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
+
+ HorizontalImageLine top_row(rows, 0, 0, width);
+ if (!FillRanges(&top_row, color_validator.get(), &nine_patch->horizontal_stretch_regions,
+ &unexpected_ranges, out_err)) {
+ return {};
+ }
+
+ if (!unexpected_ranges.empty()) {
+ const Range& range = unexpected_ranges[0];
+ std::stringstream err_stream;
+ err_stream << "found unexpected optical bounds (red pixel) on top border "
+ << "at x=" << range.start + 1;
+ *out_err = err_stream.str();
+ return {};
+ }
+
+ VerticalImageLine left_col(rows, 0, 0, height);
+ if (!FillRanges(&left_col, color_validator.get(), &nine_patch->vertical_stretch_regions,
+ &unexpected_ranges, out_err)) {
+ return {};
+ }
+
+ if (!unexpected_ranges.empty()) {
+ const Range& range = unexpected_ranges[0];
+ std::stringstream err_stream;
+ err_stream << "found unexpected optical bounds (red pixel) on left border "
+ << "at y=" << range.start + 1;
+ return {};
+ }
+
+ HorizontalImageLine bottom_row(rows, 0, height - 1, width);
+ if (!FillRanges(&bottom_row, color_validator.get(), &horizontal_padding,
+ &horizontal_layout_bounds, out_err)) {
+ return {};
+ }
+
+ if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
+ nine_patch->horizontal_stretch_regions, width - 2, &nine_patch->padding.left,
+ &nine_patch->padding.right, &nine_patch->layout_bounds.left,
+ &nine_patch->layout_bounds.right, "bottom", out_err)) {
+ return {};
+ }
+
+ VerticalImageLine right_col(rows, width - 1, 0, height);
+ if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, &vertical_layout_bounds,
+ out_err)) {
+ return {};
+ }
+
+ if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
+ nine_patch->vertical_stretch_regions, height - 2, &nine_patch->padding.top,
+ &nine_patch->padding.bottom, &nine_patch->layout_bounds.top,
+ &nine_patch->layout_bounds.bottom, "right", out_err)) {
+ return {};
+ }
+
+ // Fill the region colors of the 9-patch.
+ const int32_t num_rows = CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
+ const int32_t num_cols = CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
+ if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
+ *out_err = "too many regions in 9-patch";
+ return {};
+ }
+
+ nine_patch->region_colors.reserve(num_rows * num_cols);
+ CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
+ nine_patch->vertical_stretch_regions, width - 2, height - 2,
+ &nine_patch->region_colors);
+
+ // Compute the outline based on opacity.
+
+ // Find left and right extent of 9-patch content on center row.
+ HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
+ FindOutlineInsets(&mid_row, &nine_patch->outline.left, &nine_patch->outline.right);
+
+ // Find top and bottom extent of 9-patch content on center column.
+ VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
+ FindOutlineInsets(&mid_col, &nine_patch->outline.top, &nine_patch->outline.bottom);
+
+ const int32_t outline_width = (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
+ const int32_t outline_height =
+ (height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
+
+ // Find the largest alpha value within the outline area.
+ HorizontalImageLine outline_mid_row(rows, 1 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top + (outline_height / 2),
+ outline_width);
+ VerticalImageLine outline_mid_col(rows, 1 + nine_patch->outline.left + (outline_width / 2),
+ 1 + nine_patch->outline.top, outline_height);
+ nine_patch->outline_alpha =
+ std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
+
+ // Assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center.
+ DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, 1 + nine_patch->outline.top, 1, 1,
+ std::min(outline_width, outline_height));
+ int32_t top_left, bottom_right;
+ FindOutlineInsets(&diagonal, &top_left, &bottom_right);
+
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ nine_patch->outline_radius = 3.4142f * top_left;
+ return nine_patch;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeBase(size_t* outLen) const {
+ android::Res_png_9patch data;
+ data.numXDivs = static_cast<uint8_t>(horizontal_stretch_regions.size()) * 2;
+ data.numYDivs = static_cast<uint8_t>(vertical_stretch_regions.size()) * 2;
+ data.numColors = static_cast<uint8_t>(region_colors.size());
+ data.paddingLeft = padding.left;
+ data.paddingRight = padding.right;
+ data.paddingTop = padding.top;
+ data.paddingBottom = padding.bottom;
+
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
+ android::Res_png_9patch::serialize(data, (const int32_t*)horizontal_stretch_regions.data(),
+ (const int32_t*)vertical_stretch_regions.data(),
+ region_colors.data(), buffer.get());
+ // Convert to file endianness.
+ reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
+
+ *outLen = data.serializedSize();
+ return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(size_t* out_len) const {
+ size_t chunk_len = sizeof(uint32_t) * 4;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+ uint8_t* cursor = buffer.get();
+
+ memcpy(cursor, &layout_bounds.left, sizeof(layout_bounds.left));
+ cursor += sizeof(layout_bounds.left);
+
+ memcpy(cursor, &layout_bounds.top, sizeof(layout_bounds.top));
+ cursor += sizeof(layout_bounds.top);
+
+ memcpy(cursor, &layout_bounds.right, sizeof(layout_bounds.right));
+ cursor += sizeof(layout_bounds.right);
+
+ memcpy(cursor, &layout_bounds.bottom, sizeof(layout_bounds.bottom));
+ cursor += sizeof(layout_bounds.bottom);
+
+ *out_len = chunk_len;
+ return buffer;
+}
+
+std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(size_t* out_len) const {
+ size_t chunk_len = sizeof(uint32_t) * 6;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
+ uint8_t* cursor = buffer.get();
+
+ memcpy(cursor, &outline.left, sizeof(outline.left));
+ cursor += sizeof(outline.left);
+
+ memcpy(cursor, &outline.top, sizeof(outline.top));
+ cursor += sizeof(outline.top);
+
+ memcpy(cursor, &outline.right, sizeof(outline.right));
+ cursor += sizeof(outline.right);
+
+ memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
+ cursor += sizeof(outline.bottom);
+
+ *((float*)cursor) = outline_radius;
+ cursor += sizeof(outline_radius);
+
+ *((uint32_t*)cursor) = outline_alpha;
+
+ *out_len = chunk_len;
+ return buffer;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range) {
+ return out << "[" << range.start << ", " << range.end << ")";
+}
+
+::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
+ return out << "l=" << bounds.left << " t=" << bounds.top << " r=" << bounds.right
+ << " b=" << bounds.bottom;
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
+ for (int i = 0; i < v.size(); ++i) {
+ os << v[i];
+ if (i != v.size() - 1) os << " ";
+ }
+ return os;
+}
+
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
+ return out << "horizontalStretch:" << nine_patch.horizontal_stretch_regions
+ << " verticalStretch:" << nine_patch.vertical_stretch_regions
+ << " padding: " << nine_patch.padding << ", bounds: " << nine_patch.layout_bounds
+ << ", outline: " << nine_patch.outline << " rad=" << nine_patch.outline_radius
+ << " alpha=" << nine_patch.outline_alpha;
+}
+
+} // namespace android
diff --git a/libs/androidfw/Png.cpp b/libs/androidfw/Png.cpp
new file mode 100644
index 0000000..fb45cd9
--- /dev/null
+++ b/libs/androidfw/Png.cpp
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "androidfw/Png.h"
+
+#include <png.h>
+#include <zlib.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "android-base/strings.h"
+#include "androidfw/BigBuffer.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/Source.h"
+
+namespace android {
+
+constexpr bool kDebug = false;
+
+struct PngInfo {
+ ~PngInfo() {
+ for (png_bytep row : rows) {
+ if (row != nullptr) {
+ delete[] row;
+ }
+ }
+
+ delete[] xDivs;
+ delete[] yDivs;
+ }
+
+ void* serialize9Patch() {
+ void* serialized = Res_png_9patch::serialize(info9Patch, xDivs, yDivs, colors.data());
+ reinterpret_cast<Res_png_9patch*>(serialized)->deviceToFile();
+ return serialized;
+ }
+
+ uint32_t width = 0;
+ uint32_t height = 0;
+ std::vector<png_bytep> rows;
+
+ bool is9Patch = false;
+ Res_png_9patch info9Patch;
+ int32_t* xDivs = nullptr;
+ int32_t* yDivs = nullptr;
+ std::vector<uint32_t> colors;
+
+ // Layout padding.
+ bool haveLayoutBounds = false;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ // Round rect outline description.
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ uint8_t outlineAlpha;
+};
+
+static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
+ std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+ if (!input->read(reinterpret_cast<char*>(data), length)) {
+ png_error(readPtr, strerror(errno));
+ }
+}
+
+static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
+ BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+ png_bytep buf = outBuffer->NextBlock<png_byte>(length);
+ memcpy(buf, data, length);
+}
+
+static void flushDataToStream(png_structp /*writePtr*/) {
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+ IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+ diag->Warn(DiagMessage() << warningMessage);
+}
+
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ diag->Error(DiagMessage() << "failed reading png");
+ return false;
+ }
+
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
+ png_read_info(readPtr, infoPtr);
+
+ int colorType, bitDepth, interlaceType, compressionType;
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
+
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
+
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
+
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
+
+ png_set_interlace_handling(readPtr);
+ png_read_update_info(readPtr, infoPtr);
+
+ const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ outInfo->rows.resize(outInfo->height);
+ for (size_t i = 0; i < outInfo->height; i++) {
+ outInfo->rows[i] = new png_byte[rowBytes];
+ }
+
+ png_read_image(readPtr, outInfo->rows.data());
+ png_read_end(readPtr, infoPtr);
+ return true;
+}
+
+static void checkNinePatchSerialization(Res_png_9patch* inPatch, void* data) {
+ size_t patchSize = inPatch->serializedSize();
+ void* newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ outPatch->fileToDevice();
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+ /* for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+ }*/
+ free(newData);
+}
+
+/*static void dump_image(int w, int h, const png_byte* const* rows, int
+color_type) {
+ int i, j, rr, gg, bb, aa;
+
+ int bpp;
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type ==
+PNG_COLOR_TYPE_GRAY) {
+ bpp = 1;
+ } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ bpp = 2;
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type ==
+PNG_COLOR_TYPE_RGB_ALPHA) {
+ // We use a padding byte even when there is no alpha
+ bpp = 4;
+ } else {
+ printf("Unknown color type %d.\n", color_type);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = rows[j];
+ for (i = 0; i < w; i++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ printf("\n");
+ }
+ }
+ }
+}*/
+
+#ifdef MAX
+#undef MAX
+#endif
+#ifdef ABS
+#undef ABS
+#endif
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+
+static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
+ png_colorp rgbPalette, png_bytep alphaPalette, int* paletteEntries,
+ bool* hasTransparency, int* colorType, png_bytepp outRows) {
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
+
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+
+ if (kDebug) {
+ printf("Initial image data:\n");
+ // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ if (kDebug) {
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", maxGrayDeviation, i, j,
+ rr, gg, bb, aa);
+ }
+ }
+
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ if (kDebug) {
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j, rr, gg, bb, aa);
+ }
+ isGrayscale = false;
+ }
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ if (kDebug) {
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j, rr, gg, bb, aa);
+ }
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ if (kDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ if (kDebug) {
+ printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+ printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+ printf("isPalette = %s\n", isPalette ? "true" : "false");
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize, 2 * w * h,
+ bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+ }
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct
+ // combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is
+ // sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+ } else {
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ diag->Note(DiagMessage() << "forcing image to gray (max deviation = " << maxGrayDeviation
+ << ")");
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte)(col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
+ }
+ }
+}
+
+static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
+ int grayScaleTolerance) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ diag->Error(DiagMessage() << "failed to write png");
+ return false;
+ }
+
+ uint32_t width, height;
+ int colorType, bitDepth, interlaceType, compressionType;
+
+ png_unknown_chunk unknowns[3];
+ unknowns[0].data = nullptr;
+ unknowns[1].data = nullptr;
+ unknowns[2].data = nullptr;
+
+ png_bytepp outRows = (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
+ if (outRows == (png_bytepp)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (uint32_t i = 0; i < info->height; i++) {
+ outRows[i] = (png_bytep)malloc(2 * (int)info->width);
+ if (outRows[i] == (png_bytep)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ }
+
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+ if (kDebug) {
+ diag->Note(DiagMessage() << "writing image: w = " << info->width << ", h = " << info->height);
+ }
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, &paletteEntries,
+ &hasTransparency, &colorType, outRows);
+
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_PALETTE)) {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (kDebug) {
+ switch (colorType) {
+ case PNG_COLOR_TYPE_PALETTE:
+ diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
+ << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE");
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ diag->Note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ diag->Note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ diag->Note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+ break;
+ }
+ }
+
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p)0);
+ }
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (info->is9Patch) {
+ int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+ int pIndex = info->haveLayoutBounds ? 2 : 1;
+ int bIndex = 1;
+ int oIndex = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch
+ // data being last
+ png_bytep chunkNames =
+ info->haveLayoutBounds ? (png_bytep) "npOl\0npLb\0npTc\0" : (png_bytep) "npOl\0npTc";
+
+ // base 9 patch data
+ if (kDebug) {
+ diag->Note(DiagMessage() << "adding 9-patch info..");
+ }
+ memcpy((char*)unknowns[pIndex].name, "npTc", 5);
+ unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
+ unknowns[pIndex].size = info->info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+ // automatically generated 9 patch outline data
+ int chunkSize = sizeof(png_uint_32) * 6;
+ memcpy((char*)unknowns[oIndex].name, "npOl", 5);
+ unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
+ png_byte outputData[chunkSize];
+ memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*)outputData)[4] = info->outlineRadius;
+ ((png_uint_32*)outputData)[5] = info->outlineAlpha;
+ memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+ unknowns[oIndex].size = chunkSize;
+
+ // optional optical inset / layout bounds data
+ if (info->haveLayoutBounds) {
+ int chunkSize = sizeof(png_uint_32) * 4;
+ memcpy((char*)unknowns[bIndex].name, "npLb", 5);
+ unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
+ memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+ unknowns[bIndex].size = chunkSize;
+ }
+
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
+ }
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames, chunkCount);
+ png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+
+#if PNG_LIBPNG_VER < 10600
+ // Deal with unknown chunk location bug in 1.5.x and earlier.
+ png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+ if (info->haveLayoutBounds) {
+ png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+ }
+#endif
+ }
+
+ png_write_info(writePtr, infoPtr);
+
+ png_bytepp rows;
+ if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ }
+ rows = info->rows.data();
+ } else {
+ rows = outRows;
+ }
+ png_write_image(writePtr, rows);
+
+ if (kDebug) {
+ printf("Final image data:\n");
+ // dump_image(info->width, info->height, rows, colorType);
+ }
+
+ png_write_end(writePtr, infoPtr);
+
+ for (uint32_t i = 0; i < info->height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+ free(unknowns[2].data);
+
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
+ &compressionType, nullptr);
+
+ if (kDebug) {
+ diag->Note(DiagMessage() << "image written: w = " << width << ", h = " << height
+ << ", d = " << bitDepth << ", colors = " << colorType
+ << ", inter = " << interlaceType << ", comp = " << compressionType);
+ }
+ return true;
+}
+
+constexpr uint32_t kColorWhite = 0xffffffffu;
+constexpr uint32_t kColorTick = 0xff000000u;
+constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
+
+enum class TickType { kNone, kTick, kLayoutBounds, kBoth };
+
+static TickType tickType(png_bytep p, bool transparent, const char** outError) {
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+ if (transparent) {
+ if (p[3] == 0) {
+ return TickType::kNone;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError =
+ "Frame pixels must be either solid or transparent "
+ "(not intermediate alphas)";
+ return TickType::kNone;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in transparent frame must be black or red";
+ }
+ return TickType::kTick;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == kColorWhite) {
+ return TickType::kNone;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TickType::kNone;
+ }
+ return TickType::kTick;
+}
+
+enum class TickState { kStart, kInside1, kOutside1 };
+
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
+ int32_t* outLeft, int32_t* outRight, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
+ *outLeft = *outRight = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < width - 1; i++) {
+ if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart || (state == TickState::kOutside1 && multipleAllowed)) {
+ *outLeft = i - 1;
+ *outRight = width - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
+ return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i - 1;
+ outRight += 2;
+ outLeft += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outLeft = i;
+ return false;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return false;
+ }
+ return true;
+}
+
+static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
+ bool required, int32_t* outTop, int32_t* outBottom,
+ const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+ *outTop = *outBottom = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < height - 1; i++) {
+ if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart || (state == TickState::kOutside1 && multipleAllowed)) {
+ *outTop = i - 1;
+ *outBottom = height - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
+ return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i - 1;
+ outTop += 2;
+ outBottom += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outTop = i;
+ return false;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return false;
+ }
+ return true;
+}
+
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
+ bool /* required */, int32_t* outLeft, int32_t* outRight,
+ const char** outError) {
+ *outLeft = *outRight = 0;
+
+ // Look for left tick
+ if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for right tick
+ if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
+ bool /* required */, int32_t* outTop, int32_t* outBottom,
+ const char** outError) {
+ *outTop = *outBottom = 0;
+
+ // Look for top tick
+ if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for bottom tick
+ if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY, int dX,
+ int dY, int* outInset) {
+ uint8_t maxOpacity = 0;
+ int inset = 0;
+ *outInset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ uint8_t opacity = color[3];
+ if (opacity > maxOpacity) {
+ maxOpacity = opacity;
+ *outInset = inset;
+ }
+ if (opacity == 0xff) return;
+ }
+}
+
+static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
+ uint8_t maxAlpha = 0;
+ for (int x = startX; x < endX; x++) {
+ uint8_t alpha = (row + x * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
+}
+
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+ uint8_t maxAlpha = 0;
+ for (int y = startY; y < endY; y++) {
+ uint8_t alpha = (rows[y] + offsetX * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
+}
+
+static void getOutline(PngInfo* image) {
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
+
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
+
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
+
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
+
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineAlpha =
+ std::max(maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+
+ int diagonalInset = 0;
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+ &diagonalInset);
+
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ image->outlineRadius = 3.4142f * diagonalInset;
+
+ if (kDebug) {
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n", image->outlineInsetsLeft,
+ image->outlineInsetsTop, image->outlineInsetsRight, image->outlineInsetsBottom,
+ image->outlineRadius, image->outlineAlpha);
+ }
+}
+
+static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+ png_bytep color = rows[top] + left * 4;
+
+ if (left > right || top > bottom) {
+ return Res_png_9patch::TRANSPARENT_COLOR;
+ }
+
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top] + i * 4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return Res_png_9patch::NO_COLOR;
+ }
+ } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || p[3] != color[3]) {
+ return Res_png_9patch::NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (color[3] == 0) {
+ return Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
+}
+
+static bool do9Patch(PngInfo* image, std::string* outError) {
+ image->is9Patch = true;
+
+ int W = image->width;
+ int H = image->height;
+ int i, j;
+
+ const int maxSizeXDivs = W * sizeof(int32_t);
+ const int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->xDivs = new int32_t[W];
+ int32_t* yDivs = image->yDivs = new int32_t[H];
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
+
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+ image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
+
+ const char* errorMsg = nullptr;
+ int errorPixel = -1;
+ const char* errorEdge = nullptr;
+
+ int colorIndex = 0;
+ std::vector<png_bytep> newRows;
+
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent && (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
+ true)) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
+ &errorMsg, &numYDivs, true)) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false, &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, nullptr, false)) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
+ &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom, &errorMsg,
+ nullptr, false)) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
+ &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
+ &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds = image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
+ image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ if (kDebug) {
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+ image->layoutBoundsRight, image->layoutBoundsBottom);
+ }
+ }
+
+ // use opacity of pixels to estimate the round rect outline
+ getOutline(image);
+
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
+
+ /* if (kDebug) {
+ printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ xDivs[0], xDivs[1],
+ yDivs[0], yDivs[1]);
+ printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop,
+ image->info9Patch.paddingBottom);
+ }*/
+
+ // Remove frame from image.
+ newRows.resize(H - 2);
+ for (i = 0; i < H - 2; i++) {
+ newRows[i] = image->rows[i + 1];
+ memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+ }
+ image->rows.swap(newRows);
+
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
+
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->colors.resize(numColors);
+
+ // Fill in color information for each patch.
+
+ uint32_t c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+ if (j == numYDivs) {
+ bottom = H;
+ } else {
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+ image->colors[colorIndex++] = c;
+ if (kDebug) {
+ if (c != Res_png_9patch::NO_COLOR) {
+ hasColor = true;
+ }
+ }
+ left = right;
+ }
+ top = bottom;
+ }
+
+ assert(colorIndex == numColors);
+
+ if (kDebug && hasColor) {
+ for (i = 0; i < numColors; i++) {
+ if (i == 0) printf("Colors:\n");
+ printf(" #%08x", image->colors[i]);
+ if (i == numColors - 1) printf("\n");
+ }
+ }
+getout:
+ if (errorMsg) {
+ std::stringstream err;
+ err << "9-patch malformed: " << errorMsg;
+ if (errorEdge) {
+ err << "." << std::endl;
+ if (errorPixel >= 0) {
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+ } else {
+ err << "Found along " << errorEdge << " edge";
+ }
+ }
+ *outError = err.str();
+ return false;
+ }
+ return true;
+}
+
+bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options) {
+ png_byte signature[kPngSignatureSize];
+
+ // Read the PNG signature first.
+ if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ mDiag->Error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ mDiag->Error(DiagMessage() << "not a valid png file");
+ return false;
+ }
+
+ bool result = false;
+ png_structp readPtr = nullptr;
+ png_infop infoPtr = nullptr;
+ png_structp writePtr = nullptr;
+ png_infop writeInfoPtr = nullptr;
+ PngInfo pngInfo = {};
+
+ readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!readPtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate read ptr");
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate info ptr");
+ goto bail;
+ }
+
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
+
+ // Set the read function to read from std::istream.
+ png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
+
+ if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
+ goto bail;
+ }
+
+ if (android::base::EndsWith(source.path, ".9.png")) {
+ std::string errorMsg;
+ if (!do9Patch(&pngInfo, &errorMsg)) {
+ mDiag->Error(DiagMessage() << errorMsg);
+ goto bail;
+ }
+ }
+
+ writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!writePtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate write ptr");
+ goto bail;
+ }
+
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ mDiag->Error(DiagMessage() << "failed to allocate write info ptr");
+ goto bail;
+ }
+
+ png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
+
+ // Set the write function to write to std::ostream.
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
+
+ if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayscale_tolerance)) {
+ goto bail;
+ }
+
+ result = true;
+bail:
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
+
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
+}
+
+} // namespace android
diff --git a/libs/androidfw/PngChunkFilter.cpp b/libs/androidfw/PngChunkFilter.cpp
new file mode 100644
index 0000000..331b948
--- /dev/null
+++ b/libs/androidfw/PngChunkFilter.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "androidfw/Png.h"
+#include "androidfw/Streams.h"
+#include "androidfw/StringPiece.h"
+
+using ::android::base::StringPrintf;
+
+namespace android {
+
+static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
+
+// Useful helper function that encodes individual bytes into a uint32
+// at compile time.
+constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+ return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | ((uint32_t)d);
+}
+
+// Allow list of PNG chunk types that we want to keep in the resulting PNG.
+enum PngChunkTypes {
+ kPngChunkIHDR = u32(73, 72, 68, 82),
+ kPngChunkIDAT = u32(73, 68, 65, 84),
+ kPngChunkIEND = u32(73, 69, 78, 68),
+ kPngChunkPLTE = u32(80, 76, 84, 69),
+ kPngChunktRNS = u32(116, 82, 78, 83),
+ kPngChunksRGB = u32(115, 82, 71, 66),
+};
+
+static uint32_t Peek32LE(const char* data) {
+ uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[1]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[2]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[3]) & 0x000000ff;
+ return word;
+}
+
+static bool IsPngChunkAllowed(uint32_t type) {
+ switch (type) {
+ case kPngChunkIHDR:
+ case kPngChunkIDAT:
+ case kPngChunkIEND:
+ case kPngChunkPLTE:
+ case kPngChunktRNS:
+ case kPngChunksRGB:
+ return true;
+ default:
+ return false;
+ }
+}
+
+PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
+ if (android::base::StartsWith(data_, kPngSignature)) {
+ window_start_ = 0;
+ window_end_ = kPngSignatureSize;
+ } else {
+ error_msg_ = "file does not start with PNG signature";
+ }
+}
+
+bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) {
+ if (window_start_ != window_end_) {
+ // We have bytes to give from our window.
+ const size_t bytes_read = window_end_ - window_start_;
+ *buffer = data_.data() + window_start_;
+ *len = bytes_read;
+ window_start_ = window_end_;
+ return true;
+ }
+ return false;
+}
+
+bool PngChunkFilter::Next(const void** buffer, size_t* len) {
+ if (HadError()) {
+ return false;
+ }
+
+ // In case BackUp was called, we must consume the window.
+ if (ConsumeWindow(buffer, len)) {
+ return true;
+ }
+
+ // Advance the window as far as possible (until we meet a chunk that
+ // we want to strip).
+ while (window_end_ < data_.size()) {
+ // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
+ const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
+
+ // Is there enough room for a chunk header?
+ if (data_.size() - window_end_ < kMinChunkHeaderSize) {
+ error_msg_ = StringPrintf("Not enough space for a PNG chunk @ byte %zu/%zu", window_end_,
+ data_.size());
+ return false;
+ }
+
+ // Verify the chunk length.
+ const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
+ if ((size_t)chunk_len > data_.size() - window_end_ - kMinChunkHeaderSize) {
+ // Overflow.
+ const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+ error_msg_ = StringPrintf(
+ "PNG chunk type %08x is too large: chunk length is %zu but chunk "
+ "starts at byte %zu/%zu",
+ chunk_type, (size_t)chunk_len, window_end_ + kMinChunkHeaderSize, data_.size());
+ return false;
+ }
+
+ // Do we strip this chunk?
+ const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+ if (IsPngChunkAllowed(chunk_type)) {
+ // Advance the window to include this chunk.
+ window_end_ += kMinChunkHeaderSize + chunk_len;
+
+ // Special case the IEND chunk, which MUST appear last and libpng stops parsing once it hits
+ // such a chunk (let's do the same).
+ if (chunk_type == kPngChunkIEND) {
+ // Truncate the data to the end of this chunk. There may be garbage trailing after
+ // (b/38169876)
+ data_ = data_.substr(0, window_end_);
+ break;
+ }
+
+ } else {
+ // We want to strip this chunk. If we accumulated a window,
+ // we must return the window now.
+ if (window_start_ != window_end_) {
+ break;
+ }
+
+ // The window is empty, so we can advance past this chunk
+ // and keep looking for the next good chunk,
+ window_end_ += kMinChunkHeaderSize + chunk_len;
+ window_start_ = window_end_;
+ }
+ }
+
+ if (ConsumeWindow(buffer, len)) {
+ return true;
+ }
+ return false;
+}
+
+void PngChunkFilter::BackUp(size_t count) {
+ if (HadError()) {
+ return;
+ }
+ window_start_ -= count;
+}
+
+bool PngChunkFilter::Rewind() {
+ if (HadError()) {
+ return false;
+ }
+ window_start_ = 0;
+ window_end_ = kPngSignatureSize;
+ return true;
+}
+} // namespace android
diff --git a/libs/androidfw/PngCrunch.cpp b/libs/androidfw/PngCrunch.cpp
new file mode 100644
index 0000000..cf3c0ee
--- /dev/null
+++ b/libs/androidfw/PngCrunch.cpp
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <png.h>
+#include <zlib.h>
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "android-base/errors.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/Png.h"
+
+namespace android {
+
+// Custom deleter that destroys libpng read and info structs.
+class PngReadStructDeleter {
+ public:
+ PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
+ : read_ptr_(read_ptr), info_ptr_(info_ptr) {
+ }
+
+ ~PngReadStructDeleter() {
+ png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
+ }
+
+ private:
+ png_structp read_ptr_;
+ png_infop info_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
+};
+
+// Custom deleter that destroys libpng write and info structs.
+class PngWriteStructDeleter {
+ public:
+ PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
+ : write_ptr_(write_ptr), info_ptr_(info_ptr) {
+ }
+
+ ~PngWriteStructDeleter() {
+ png_destroy_write_struct(&write_ptr_, &info_ptr_);
+ }
+
+ private:
+ png_structp write_ptr_;
+ png_infop info_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
+};
+
+// Custom warning logging method that uses IDiagnostics.
+static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
+ android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
+ diag->Warn(android::DiagMessage() << warning_msg);
+}
+
+// Custom error logging method that uses IDiagnostics.
+static void LogError(png_structp png_ptr, png_const_charp error_msg) {
+ android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
+ diag->Error(android::DiagMessage() << error_msg);
+
+ // Causes libpng to longjmp to the spot where setjmp was set. This is how libpng does
+ // error handling. If this custom error handler method were to return, libpng would, by
+ // default, print the error message to stdout and call the same png_longjmp method.
+ png_longjmp(png_ptr, 1);
+}
+
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
+ InputStream* in = (InputStream*)png_get_io_ptr(png_ptr);
+
+ const void* in_buffer;
+ size_t in_len;
+ if (!in->Next(&in_buffer, &in_len)) {
+ if (in->HadError()) {
+ std::stringstream error_msg_builder;
+ error_msg_builder << "failed reading from input";
+ if (!in->GetError().empty()) {
+ error_msg_builder << ": " << in->GetError();
+ }
+ std::string err = error_msg_builder.str();
+ png_error(png_ptr, err.c_str());
+ }
+ return;
+ }
+
+ const size_t bytes_read = std::min(in_len, len);
+ memcpy(buffer, in_buffer, bytes_read);
+ if (bytes_read != in_len) {
+ in->BackUp(in_len - bytes_read);
+ }
+}
+
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
+ OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr);
+
+ void* out_buffer;
+ size_t out_len;
+ while (len > 0) {
+ if (!out->Next(&out_buffer, &out_len)) {
+ if (out->HadError()) {
+ std::stringstream err_msg_builder;
+ err_msg_builder << "failed writing to output";
+ if (!out->GetError().empty()) {
+ err_msg_builder << ": " << out->GetError();
+ }
+ std::string err = out->GetError();
+ png_error(png_ptr, err.c_str());
+ }
+ return;
+ }
+
+ const size_t bytes_written = std::min(out_len, len);
+ memcpy(out_buffer, buffer, bytes_written);
+
+ // Advance the input buffer.
+ buffer += bytes_written;
+ len -= bytes_written;
+
+ // Advance the output buffer.
+ out_len -= bytes_written;
+ }
+
+ // If the entire output buffer wasn't used, backup.
+ if (out_len > 0) {
+ out->BackUp(out_len);
+ }
+}
+
+std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) {
+ // Read the first 8 bytes of the file looking for the PNG signature.
+ // Bail early if it does not match.
+ const png_byte* signature;
+ size_t buffer_size;
+ if (!in->Next((const void**)&signature, &buffer_size)) {
+ if (in->HadError()) {
+ diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError());
+ } else {
+ diag->Error(android::DiagMessage() << "not enough data for PNG signature");
+ }
+ return {};
+ }
+
+ if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ diag->Error(android::DiagMessage() << "file signature does not match PNG signature");
+ return {};
+ }
+
+ // Start at the beginning of the first chunk.
+ in->BackUp(buffer_size - kPngSignatureSize);
+
+ // Create and initialize the png_struct with the default error and warning handlers.
+ // The header version is also passed in to ensure that this was built against the same
+ // version of libpng.
+ png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (read_ptr == nullptr) {
+ diag->Error(android::DiagMessage() << "failed to create libpng read png_struct");
+ return {};
+ }
+
+ // Create and initialize the memory for image header and data.
+ png_infop info_ptr = png_create_info_struct(read_ptr);
+ if (info_ptr == nullptr) {
+ diag->Error(android::DiagMessage() << "failed to create libpng read png_info");
+ png_destroy_read_struct(&read_ptr, nullptr, nullptr);
+ return {};
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
+
+ // libpng uses longjmp to jump to an error handling routine.
+ // setjmp will only return true if it was jumped to, aka there was
+ // an error.
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ return {};
+ }
+
+ // Handle warnings ourselves via IDiagnostics.
+ png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning);
+
+ // Set up the read functions which read from our custom data sources.
+ png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
+
+ // Skip the signature that we already read.
+ png_set_sig_bytes(read_ptr, kPngSignatureSize);
+
+ // Read the chunk headers.
+ png_read_info(read_ptr, info_ptr);
+
+ // Extract image meta-data from the various chunk headers.
+ uint32_t width, height;
+ int bit_depth, color_type, interlace_method, compression_method, filter_method;
+ png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method,
+ &compression_method, &filter_method);
+
+ // When the image is read, expand it so that it is in RGBA 8888 format
+ // so that image handling is uniform.
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(read_ptr);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
+ png_set_expand_gray_1_2_4_to_8(read_ptr);
+ }
+
+ if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(read_ptr);
+ }
+
+ if (bit_depth == 16) {
+ png_set_strip_16(read_ptr);
+ }
+
+ if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(read_ptr);
+ }
+
+ if (interlace_method != PNG_INTERLACE_NONE) {
+ png_set_interlace_handling(read_ptr);
+ }
+
+ // Once all the options for reading have been set, we need to flush
+ // them to libpng.
+ png_read_update_info(read_ptr, info_ptr);
+
+ // 9-patch uses int32_t to index images, so we cap the image dimensions to
+ // something
+ // that can always be represented by 9-patch.
+ if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
+ diag->Error(android::DiagMessage()
+ << "PNG image dimensions are too large: " << width << "x" << height);
+ return {};
+ }
+
+ std::unique_ptr<Image> output_image = std::make_unique<Image>();
+ output_image->width = static_cast<int32_t>(width);
+ output_image->height = static_cast<int32_t>(height);
+
+ const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
+ CHECK(row_bytes == 4 * width); // RGBA
+
+ // Allocate one large block to hold the image.
+ output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+
+ // Create an array of rows that index into the data block.
+ output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
+ for (uint32_t h = 0; h < height; h++) {
+ output_image->rows[h] = output_image->data.get() + (h * row_bytes);
+ }
+
+ // Actually read the image pixels.
+ png_read_image(read_ptr, output_image->rows.get());
+
+ // Finish reading. This will read any other chunks after the image data.
+ png_read_end(read_ptr, info_ptr);
+
+ return output_image;
+}
+
+// Experimentally chosen constant to be added to the overhead of using color type
+// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
+// Without this, many small PNGs encoded with palettes are larger after compression than
+// the same PNGs encoded as RGBA.
+constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
+
+// Pick a color type by which to encode the image, based on which color type will take
+// the least amount of disk space.
+//
+// 9-patch images traditionally have not been encoded with palettes.
+// The original rationale was to avoid dithering until after scaling,
+// but I don't think this would be an issue with palettes. Either way,
+// our naive size estimation tends to be wrong for small images like 9-patches
+// and using palettes balloons the size of the resulting 9-patch.
+// In order to not regress in size, restrict 9-patch to not use palettes.
+
+// The options are:
+//
+// - RGB
+// - RGBA
+// - RGB + cheap alpha
+// - Color palette
+// - Color palette + cheap alpha
+// - Color palette + alpha palette
+// - Grayscale
+// - Grayscale + cheap alpha
+// - Grayscale + alpha
+//
+static int PickColorType(int32_t width, int32_t height, bool grayscale,
+ bool convertible_to_grayscale, bool has_nine_patch,
+ size_t color_palette_size, size_t alpha_palette_size) {
+ const size_t palette_chunk_size = 16 + color_palette_size * 3;
+ const size_t alpha_chunk_size = 16 + alpha_palette_size;
+ const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
+ const size_t color_data_chunk_size = 16 + 3 * width * height;
+ const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
+ const size_t palette_data_chunk_size = 16 + width * height;
+
+ if (grayscale) {
+ if (alpha_palette_size == 0) {
+ // This is the smallest the data can be.
+ return PNG_COLOR_TYPE_GRAY;
+ } else if (color_palette_size <= 256 && !has_nine_patch) {
+ // This grayscale has alpha and can fit within a palette.
+ // See if it is worth fitting into a palette.
+ const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
+ palette_data_chunk_size + kPaletteOverheadConstant;
+ if (grayscale_alpha_data_chunk_size > palette_threshold) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ if (color_palette_size <= 256 && !has_nine_patch) {
+ // This image can fit inside a palette. Let's see if it is worth it.
+ size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size;
+ size_t total_size_without_palette = color_data_chunk_size;
+ if (alpha_palette_size > 0) {
+ total_size_with_palette += alpha_palette_size;
+ total_size_without_palette = color_alpha_data_chunk_size;
+ }
+
+ if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+
+ if (convertible_to_grayscale) {
+ if (alpha_palette_size == 0) {
+ return PNG_COLOR_TYPE_GRAY;
+ } else {
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+ }
+
+ if (alpha_palette_size == 0) {
+ return PNG_COLOR_TYPE_RGB;
+ }
+ return PNG_COLOR_TYPE_RGBA;
+}
+
+// Assigns indices to the color and alpha palettes, encodes them, and then invokes
+// png_set_PLTE/png_set_tRNS.
+// This must be done before writing image data.
+// Image data must be transformed to use the indices assigned within the palette.
+static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
+ std::unordered_map<uint32_t, int>* color_palette,
+ std::unordered_set<uint32_t>* alpha_palette) {
+ CHECK(color_palette->size() <= 256);
+ CHECK(alpha_palette->size() <= 256);
+
+ // Populate the PNG palette struct and assign indices to the color palette.
+
+ // Colors in the alpha palette should have smaller indices.
+ // This will ensure that we can truncate the alpha palette if it is
+ // smaller than the color palette.
+ int index = 0;
+ for (uint32_t color : *alpha_palette) {
+ (*color_palette)[color] = index++;
+ }
+
+ // Assign the rest of the entries.
+ for (auto& entry : *color_palette) {
+ if (entry.second == -1) {
+ entry.second = index++;
+ }
+ }
+
+ // Create the PNG color palette struct.
+ auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+
+ std::unique_ptr<png_byte[]> alpha_palette_bytes;
+ if (!alpha_palette->empty()) {
+ alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+ }
+
+ for (const auto& entry : *color_palette) {
+ const uint32_t color = entry.first;
+ const int index = entry.second;
+ CHECK(index >= 0);
+ CHECK(static_cast<size_t>(index) < color_palette->size());
+
+ png_colorp slot = color_palette_bytes.get() + index;
+ slot->red = color >> 24;
+ slot->green = color >> 16;
+ slot->blue = color >> 8;
+
+ const png_byte alpha = color & 0x000000ff;
+ if (alpha != 0xff && alpha_palette_bytes) {
+ CHECK(static_cast<size_t>(index) < alpha_palette->size());
+ alpha_palette_bytes[index] = alpha;
+ }
+ }
+
+ // The bytes get copied here, so it is safe to release color_palette_bytes at
+ // the end of function
+ // scope.
+ png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
+
+ if (alpha_palette_bytes) {
+ png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
+ nullptr);
+ }
+}
+
+// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
+// before writing image data.
+static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
+ const NinePatch* nine_patch) {
+ // The order of the chunks is important.
+ // 9-patch code in older platforms expects the 9-patch chunk to be last.
+
+ png_unknown_chunk unknown_chunks[3];
+ memset(unknown_chunks, 0, sizeof(unknown_chunks));
+
+ size_t index = 0;
+ size_t chunk_len = 0;
+
+ std::unique_ptr<uint8_t[]> serialized_outline =
+ nine_patch->SerializeRoundedRectOutline(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npOl");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_outline.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ std::unique_ptr<uint8_t[]> serialized_layout_bounds;
+ if (nine_patch->layout_bounds.nonZero()) {
+ serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npLb");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+ }
+
+ std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
+ strcpy((char*)unknown_chunks[index].name, "npTc");
+ unknown_chunks[index].size = chunk_len;
+ unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
+ unknown_chunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ // Handle all unknown chunks. We are manually setting the chunks here,
+ // so we will only ever handle our custom chunks.
+ png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
+
+ // Set the actual chunks here. The data gets copied, so our buffers can
+ // safely go out of scope.
+ png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
+}
+
+bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
+ const PngOptions& options, IDiagnostics* diag, bool verbose) {
+ // Create and initialize the write png_struct with the default error and
+ // warning handlers.
+ // The header version is also passed in to ensure that this was built against the same
+ // version of libpng.
+ png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (write_ptr == nullptr) {
+ diag->Error(android::DiagMessage() << "failed to create libpng write png_struct");
+ return false;
+ }
+
+ // Allocate memory to store image header data.
+ png_infop write_info_ptr = png_create_info_struct(write_ptr);
+ if (write_info_ptr == nullptr) {
+ diag->Error(android::DiagMessage() << "failed to create libpng write png_info");
+ png_destroy_write_struct(&write_ptr, nullptr);
+ return false;
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
+
+ // libpng uses longjmp to jump to error handling routines.
+ // setjmp will return true only if it was jumped to, aka, there was an error.
+ if (setjmp(png_jmpbuf(write_ptr))) {
+ return false;
+ }
+
+ // Handle warnings with our IDiagnostics.
+ png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning);
+
+ // Set up the write functions which write to our custom data sources.
+ png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
+
+ // We want small files and can take the performance hit to achieve this goal.
+ png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+
+ // Begin analysis of the image data.
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors (palette).
+ std::unordered_map<uint32_t, int> color_palette;
+ std::unordered_set<uint32_t> alpha_palette;
+ bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
+ bool grayscale = true;
+ int max_gray_deviation = 0;
+
+ for (int32_t y = 0; y < image->height; y++) {
+ const uint8_t* row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int red = *row++;
+ int green = *row++;
+ int blue = *row++;
+ int alpha = *row++;
+
+ if (alpha == 0) {
+ // The color is completely transparent.
+ // For purposes of palettes and grayscale optimization,
+ // treat all channels as 0x00.
+ needs_to_zero_rgb_channels_of_transparent_pixels =
+ needs_to_zero_rgb_channels_of_transparent_pixels ||
+ (red != 0 || green != 0 || blue != 0);
+ red = green = blue = 0;
+ }
+
+ // Insert the color into the color palette.
+ const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
+ color_palette[color] = -1;
+
+ // If the pixel has non-opaque alpha, insert it into the
+ // alpha palette.
+ if (alpha != 0xff) {
+ alpha_palette.insert(color);
+ }
+
+ // Check if the image is indeed grayscale.
+ if (grayscale) {
+ if (red != green || red != blue) {
+ grayscale = false;
+ }
+ }
+
+ // Calculate the gray scale deviation so that it can be compared
+ // with the threshold.
+ max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
+ max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
+ max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
+ }
+ }
+
+ if (verbose) {
+ android::DiagMessage msg;
+ msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size()
+ << " maxGrayDeviation=" << max_gray_deviation
+ << " grayScale=" << (grayscale ? "true" : "false");
+ diag->Note(msg);
+ }
+
+ const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
+
+ const int new_color_type =
+ PickColorType(image->width, image->height, grayscale, convertible_to_grayscale,
+ nine_patch != nullptr, color_palette.size(), alpha_palette.size());
+
+ if (verbose) {
+ android::DiagMessage msg;
+ msg << "encoding PNG ";
+ if (nine_patch) {
+ msg << "(with 9-patch) as ";
+ }
+ switch (new_color_type) {
+ case PNG_COLOR_TYPE_GRAY:
+ msg << "GRAY";
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ msg << "GRAY + ALPHA";
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ msg << "RGB";
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ msg << "RGBA";
+ break;
+ case PNG_COLOR_TYPE_PALETTE:
+ msg << "PALETTE";
+ break;
+ default:
+ msg << "unknown type " << new_color_type;
+ break;
+ }
+ diag->Note(msg);
+ }
+
+ png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (new_color_type & PNG_COLOR_MASK_PALETTE) {
+ // Assigns indices to the palette, and writes the encoded palette to the
+ // libpng writePtr.
+ WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
+ png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (nine_patch) {
+ WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
+ }
+
+ // Flush our updates to the header.
+ png_write_info(write_ptr, write_info_ptr);
+
+ // Write out each row of image data according to its encoding.
+ if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
+ // 1 byte/pixel.
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *in_row++;
+ int gg = *in_row++;
+ int bb = *in_row++;
+ int aa = *in_row++;
+ if (aa == 0) {
+ // Zero out color channels when transparent.
+ rr = gg = bb = 0;
+ }
+
+ const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
+ const int idx = color_palette[color];
+ CHECK(idx != -1);
+ out_row[x] = static_cast<png_byte>(idx);
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = in_row[x * 4];
+ int gg = in_row[x * 4 + 1];
+ int bb = in_row[x * 4 + 2];
+ int aa = in_row[x * 4 + 3];
+ if (aa == 0) {
+ // Zero out the gray channel when transparent.
+ rr = gg = bb = 0;
+ }
+
+ if (grayscale) {
+ // The image was already grayscale, red == green == blue.
+ out_row[x * bpp] = in_row[x * 4];
+ } else {
+ // The image is convertible to grayscale, use linear-luminance of
+ // sRGB colorspace:
+ // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
+ out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+
+ if (bpp == 2) {
+ // Write out alpha if we have it.
+ out_row[x * bpp + 1] = aa;
+ }
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
+ const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
+ if (needs_to_zero_rgb_channels_of_transparent_pixels) {
+ // The source RGBA data can't be used as-is, because we need to zero out
+ // the RGB values of transparent pixels.
+ auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep in_row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *in_row++;
+ int gg = *in_row++;
+ int bb = *in_row++;
+ int aa = *in_row++;
+ if (aa == 0) {
+ // Zero out the RGB channels when transparent.
+ rr = gg = bb = 0;
+ }
+ out_row[x * bpp] = rr;
+ out_row[x * bpp + 1] = gg;
+ out_row[x * bpp + 2] = bb;
+ if (bpp == 4) {
+ out_row[x * bpp + 3] = aa;
+ }
+ }
+ png_write_row(write_ptr, out_row.get());
+ }
+ } else {
+ // The source image can be used as-is, just tell libpng whether or not to
+ // ignore the alpha channel.
+ if (new_color_type == PNG_COLOR_TYPE_RGB) {
+ // Delete the extraneous alpha values that we appended to our buffer
+ // when reading the original values.
+ png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+ }
+ png_write_image(write_ptr, image->rows.get());
+ }
+ } else {
+ LOG(FATAL) << "unreachable";
+ }
+
+ png_write_end(write_ptr, write_info_ptr);
+ return true;
+}
+
+} // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index c13827f..4c992be 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1769,13 +1769,21 @@
status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
{
+ const ResChunk_header* chunk = nullptr;
+ const ResChunk_header* lastChunk = nullptr;
+
uninit();
mEventCode = START_DOCUMENT;
if (!data || !size) {
return (mError=BAD_TYPE);
}
-
+ if (size < sizeof(ResXMLTree_header)) {
+ ALOGW("Bad XML block: total size %d is less than the header size %d\n",
+ int(size), int(sizeof(ResXMLTree_header)));
+ mError = BAD_TYPE;
+ goto done;
+ }
if (copyData) {
mOwnedData = malloc(size);
if (mOwnedData == NULL) {
@@ -1792,9 +1800,15 @@
(int)dtohs(mHeader->header.headerSize),
(int)dtohl(mHeader->header.size), (int)size);
mError = BAD_TYPE;
- restart();
- return mError;
+ goto done;
}
+ if (dtohs(mHeader->header.type) != RES_XML_TYPE) {
+ ALOGW("Bad XML block: expected root block type %d, got %d\n",
+ int(RES_XML_TYPE), int(dtohs(mHeader->header.type)));
+ mError = BAD_TYPE;
+ goto done;
+ }
+
mDataEnd = ((const uint8_t*)mHeader) + mSize;
mStrings.uninit();
@@ -1804,9 +1818,8 @@
// First look for a couple interesting chunks: the string block
// and first XML node.
- const ResChunk_header* chunk =
- (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
- const ResChunk_header* lastChunk = chunk;
+ chunk = (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
+ lastChunk = chunk;
while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML");
@@ -1860,7 +1873,11 @@
mError = mStrings.getError();
done:
- restart();
+ if (mError) {
+ uninit();
+ } else {
+ restart();
+ }
return mError;
}
@@ -2551,6 +2568,22 @@
return false;
}
+bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+ }
+ return false;
+}
+
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
@@ -5436,37 +5469,66 @@
return U16StringToInt(s, len, outValue);
}
-bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
-{
- while (len > 0 && isspace16(*s)) {
- s++;
- len--;
+template <typename T>
+bool parseFloatingPoint(const char16_t* inBuf, size_t inLen, char* tempBuf,
+ const char** outEnd, T& out){
+ while (inLen > 0 && isspace16(*inBuf)) {
+ inBuf++;
+ inLen--;
}
- if (len <= 0) {
+ if (inLen <= 0) {
return false;
}
- char buf[128];
int i=0;
- while (len > 0 && *s != 0 && i < 126) {
- if (*s > 255) {
+ while (inLen > 0 && *inBuf != 0 && i < 126) {
+ if (*inBuf > 255) {
return false;
}
- buf[i++] = *s++;
- len--;
+ tempBuf[i++] = *inBuf++;
+ inLen--;
}
- if (len > 0) {
+ if (inLen > 0) {
return false;
}
- if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') {
+ if ((tempBuf[0] < '0' || tempBuf[0] > '9') && tempBuf[0] != '.' && tempBuf[0] != '-' && tempBuf[0] != '+') {
return false;
}
- buf[i] = 0;
- const char* end;
- float f = strtof(buf, (char**)&end);
+ tempBuf[i] = 0;
+ if constexpr(std::is_same_v<T, float>) {
+ out = strtof(tempBuf, (char**)outEnd);
+ } else {
+ out = strtod(tempBuf, (char**)outEnd);
+ }
+ return true;
+}
+
+bool ResTable::stringToDouble(const char16_t* s, size_t len, double& d){
+ char buf[128];
+ const char* end = nullptr;
+ if (!parseFloatingPoint(s, len, buf, &end, d)) {
+ return false;
+ }
+
+ while (*end != 0 && isspace((unsigned char)*end)) {
+ end++;
+ }
+
+ return *end == 0;
+}
+
+bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
+{
+ char buf[128];
+ const char* end = nullptr;
+ float f;
+
+ if (!parseFloatingPoint(s, len, buf, &end, f)) {
+ return false;
+ }
if (*end != 0 && !isspace((unsigned char)*end)) {
// Might be a unit...
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 52e7a70..d7b5914 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -40,17 +40,24 @@
public:
ZipEntry entry;
std::string_view name;
- void *cookie;
+ void *cookie = nullptr;
- _ZipEntryRO() : cookie(NULL) {}
+ _ZipEntryRO() = default;
~_ZipEntryRO() {
- EndIteration(cookie);
+ EndIteration(cookie);
+ }
+
+ android::ZipEntryRO convertToPtr() {
+ _ZipEntryRO* result = new _ZipEntryRO;
+ result->entry = std::move(this->entry);
+ result->name = std::move(this->name);
+ result->cookie = std::exchange(this->cookie, nullptr);
+ return result;
}
private:
- _ZipEntryRO(const _ZipEntryRO& other);
- _ZipEntryRO& operator=(const _ZipEntryRO& other);
+ DISALLOW_COPY_AND_ASSIGN(_ZipEntryRO);
};
ZipFileRO::~ZipFileRO() {
@@ -94,17 +101,15 @@
ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
{
- _ZipEntryRO* data = new _ZipEntryRO;
+ _ZipEntryRO data;
+ data.name = entryName;
- data->name = entryName;
-
- const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
+ const int32_t error = FindEntry(mHandle, entryName, &(data.entry));
if (error) {
- delete data;
- return NULL;
+ return nullptr;
}
- return (ZipEntryRO) data;
+ return data.convertToPtr();
}
/*
@@ -143,35 +148,50 @@
}
bool ZipFileRO::startIteration(void** cookie) {
- return startIteration(cookie, NULL, NULL);
+ return startIteration(cookie, nullptr, nullptr);
}
-bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix)
-{
- _ZipEntryRO* ze = new _ZipEntryRO;
- int32_t error = StartIteration(mHandle, &(ze->cookie),
+bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) {
+ auto result = startIterationOrError(prefix, suffix);
+ if (!result.ok()) {
+ return false;
+ }
+ *cookie = result.value();
+ return true;
+}
+
+base::expected<void*, int32_t>
+ZipFileRO::startIterationOrError(const char* prefix, const char* suffix) {
+ _ZipEntryRO ze;
+ int32_t error = StartIteration(mHandle, &(ze.cookie),
prefix ? prefix : "", suffix ? suffix : "");
if (error) {
ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
ErrorCodeString(error));
- delete ze;
- return false;
+ return base::unexpected(error);
}
- *cookie = ze;
- return true;
+ return ze.convertToPtr();
}
-ZipEntryRO ZipFileRO::nextEntry(void* cookie)
-{
+ZipEntryRO ZipFileRO::nextEntry(void* cookie) {
+ auto result = nextEntryOrError(cookie);
+ if (!result.ok()) {
+ return nullptr;
+ }
+ return result.value();
+}
+
+base::expected<ZipEntryRO, int32_t> ZipFileRO::nextEntryOrError(void* cookie) {
_ZipEntryRO* ze = reinterpret_cast<_ZipEntryRO*>(cookie);
int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
if (error) {
if (error != -1) {
ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
ErrorCodeString(error));
+ return base::unexpected(error);
}
- return NULL;
+ return nullptr;
}
return &(ze->entry);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index fc391bc..d9ff35b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
using ApkAssetsWPtr = wp<const ApkAssets>;
using ApkAssetsList = std::span<const ApkAssetsPtr>;
- AssetManager2() = default;
+ AssetManager2();
explicit AssetManager2(AssetManager2&& other) = default;
AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
@@ -156,10 +156,14 @@
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfiguration(const ResTable_config& configuration);
+ void SetConfigurations(std::vector<ResTable_config> configurations);
- inline const ResTable_config& GetConfiguration() const {
- return configuration_;
+ inline const std::vector<ResTable_config>& GetConfigurations() const {
+ return configurations_;
+ }
+
+ inline void SetDefaultLocale(uint32_t default_locale) {
+ default_locale_ = default_locale;
}
// Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -243,9 +247,14 @@
friend AssetManager2;
friend Theme;
SelectedValue() = default;
- SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry) :
- cookie(entry.cookie), data(entry.value.data), type(entry.value.dataType),
- flags(bag->type_spec_flags), resid(0U), config({}) {};
+ SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry)
+ : cookie(entry.cookie),
+ data(entry.value.data),
+ type(entry.value.dataType),
+ flags(bag->type_spec_flags),
+ resid(0U),
+ config() {
+ }
// The cookie representing the ApkAssets in which the value resides.
ApkAssetsCookie cookie = kInvalidCookie;
@@ -327,7 +336,8 @@
// resource data failed.
base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const;
- const std::vector<uint32_t> GetBagResIdStack(uint32_t resid) const;
+ base::expected<const std::vector<uint32_t>*, NullOrIOError> GetBagResIdStack(
+ uint32_t resid) const;
// Resets the resource resolution structures in preparation for the next resource retrieval.
void ResetResourceResolution() const;
@@ -459,9 +469,11 @@
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
- // The current configuration set for this AssetManager. When this changes, cached resources
+ uint32_t default_locale_;
+
+ // The current configurations set for this AssetManager. When this changes, cached resources
// may need to be purged.
- ResTable_config configuration_ = {};
+ std::vector<ResTable_config> configurations_;
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
@@ -495,10 +507,10 @@
// Marks what kind of override this step was.
Type type;
+ ApkAssetsCookie cookie = kInvalidCookie;
+
// Built name of configuration for this step.
String8 config_name;
-
- ApkAssetsCookie cookie = kInvalidCookie;
};
// Last resolved resource ID.
diff --git a/libs/androidfw/include/androidfw/BigBufferStream.h b/libs/androidfw/include/androidfw/BigBufferStream.h
new file mode 100644
index 0000000..c23194b
--- /dev/null
+++ b/libs/androidfw/include/androidfw/BigBufferStream.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include "BigBuffer.h"
+#include "Streams.h"
+
+namespace android {
+
+class BigBufferInputStream : public KnownSizeInputStream {
+ public:
+ inline explicit BigBufferInputStream(const BigBuffer* buffer)
+ : owning_buffer_(0), buffer_(buffer), iter_(buffer->begin()) {
+ }
+
+ inline explicit BigBufferInputStream(android::BigBuffer&& buffer)
+ : owning_buffer_(std::move(buffer)), buffer_(&owning_buffer_), iter_(buffer_->begin()) {
+ }
+
+ virtual ~BigBufferInputStream() = default;
+
+ bool Next(const void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ bool CanRewind() const override;
+
+ bool Rewind() override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ size_t TotalSize() const override;
+
+ bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+
+ android::BigBuffer owning_buffer_;
+ const BigBuffer* buffer_;
+ BigBuffer::const_iterator iter_;
+ size_t offset_ = 0;
+ size_t bytes_read_ = 0;
+};
+
+class BigBufferOutputStream : public OutputStream {
+ public:
+ inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+ }
+ virtual ~BigBufferOutputStream() = default;
+
+ bool Next(void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+ BigBuffer* buffer_;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/Errors.h b/libs/androidfw/include/androidfw/Errors.h
index 948162d..6667747 100644
--- a/libs/androidfw/include/androidfw/Errors.h
+++ b/libs/androidfw/include/androidfw/Errors.h
@@ -34,7 +34,7 @@
// Checks whether the result holds an unexpected I/O error.
template <typename T>
-static inline bool IsIOError(const base::expected<T, NullOrIOError> result) {
+static inline bool IsIOError(const base::expected<T, NullOrIOError>& result) {
return !result.has_value() && std::holds_alternative<IOError>(result.error());
}
diff --git a/libs/androidfw/include/androidfw/FileStream.h b/libs/androidfw/include/androidfw/FileStream.h
new file mode 100644
index 0000000..87c42d1
--- /dev/null
+++ b/libs/androidfw/include/androidfw/FileStream.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unistd.h>
+
+#include "Streams.h"
+#include "android-base/macros.h"
+#include "android-base/unique_fd.h"
+
+namespace android {
+
+constexpr size_t kDefaultBufferCapacity = 4096u;
+
+class FileInputStream : public InputStream {
+ public:
+ explicit FileInputStream(const std::string& path,
+ size_t buffer_capacity = kDefaultBufferCapacity);
+
+ // Take ownership of `fd`.
+ explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
+
+ // Take ownership of `fd`.
+ explicit FileInputStream(android::base::borrowed_fd fd,
+ size_t buffer_capacity = kDefaultBufferCapacity);
+
+ ~FileInputStream() {
+ if (should_close_ && (fd_ != -1)) {
+ close(fd_);
+ }
+ }
+
+ bool Next(const void** data, size_t* size) override;
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ std::string GetError() const override;
+
+ bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileInputStream);
+
+ int fd_ = -1;
+ std::string error_;
+ bool should_close_;
+ std::unique_ptr<uint8_t[]> buffer_;
+ size_t buffer_capacity_ = 0u;
+ size_t buffer_offset_ = 0u;
+ size_t buffer_size_ = 0u;
+ size_t total_byte_count_ = 0u;
+};
+
+class FileOutputStream : public OutputStream {
+ public:
+ explicit FileOutputStream(const std::string& path,
+ size_t buffer_capacity = kDefaultBufferCapacity);
+
+ // Does not take ownership of `fd`.
+ explicit FileOutputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
+
+ // Takes ownership of `fd`.
+ explicit FileOutputStream(android::base::unique_fd fd,
+ size_t buffer_capacity = kDefaultBufferCapacity);
+
+ ~FileOutputStream();
+
+ bool Next(void** data, size_t* size) override;
+
+ // Immediately flushes out the contents of the buffer to disk.
+ bool Flush();
+
+ void BackUp(size_t count) override;
+
+ size_t ByteCount() const override;
+
+ bool HadError() const override;
+
+ std::string GetError() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileOutputStream);
+
+ bool FlushImpl();
+
+ android::base::unique_fd owned_fd_;
+ int fd_;
+ std::string error_;
+ std::unique_ptr<uint8_t[]> buffer_;
+ size_t buffer_capacity_ = 0u;
+ size_t buffer_offset_ = 0u;
+ size_t total_byte_count_ = 0u;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 4d5844e..d1dda81 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -17,10 +17,15 @@
#ifndef _ANDROID_DIAGNOSTICS_H
#define _ANDROID_DIAGNOSTICS_H
+// on some systems ERROR is defined as 0 so android::base::ERROR becomes android::base::0
+// which doesn't compile. We undef it here to avoid that and because we don't ever need that def.
+#undef ERROR
+
#include <sstream>
#include <string>
#include "Source.h"
+#include "android-base/logging.h"
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
@@ -86,6 +91,17 @@
DiagMessageActual actual = message.Build();
Log(Level::Note, actual);
}
+
+ virtual void SetVerbose(bool val) {
+ verbose_ = val;
+ }
+
+ virtual bool IsVerbose() {
+ return verbose_;
+ }
+
+ private:
+ bool verbose_ = false;
};
class SourcePathDiagnostics : public IDiagnostics {
@@ -105,6 +121,14 @@
return error;
}
+ void SetVerbose(bool val) override {
+ diag_->SetVerbose(val);
+ }
+
+ bool IsVerbose() override {
+ return diag_->IsVerbose();
+ }
+
private:
Source source_;
IDiagnostics* diag_;
@@ -125,6 +149,36 @@
DISALLOW_COPY_AND_ASSIGN(NoOpDiagnostics);
};
+class AndroidLogDiagnostics : public IDiagnostics {
+ public:
+ AndroidLogDiagnostics() = default;
+
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ android::base::LogSeverity severity;
+ switch (level) {
+ case Level::Error:
+ severity = android::base::ERROR;
+ break;
+
+ case Level::Warn:
+ severity = android::base::WARNING;
+ break;
+
+ case Level::Note:
+ severity = android::base::INFO;
+ break;
+ }
+ if (!actual_msg.source.path.empty()) {
+ LOG(severity) << actual_msg.source << ": " + actual_msg.message;
+ } else {
+ LOG(severity) << actual_msg.message;
+ }
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AndroidLogDiagnostics);
+};
+
+
} // namespace android
#endif /* _ANDROID_DIAGNOSTICS_H */
diff --git a/libs/androidfw/include/androidfw/Image.h b/libs/androidfw/include/androidfw/Image.h
new file mode 100644
index 0000000..c18c34c
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Image.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+/**
+ * An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
+ */
+class Image {
+ public:
+ explicit Image() = default;
+
+ /**
+ * A `height` sized array of pointers, where each element points to a
+ * `width` sized row of RGBA_8888 pixels.
+ */
+ std::unique_ptr<uint8_t*[]> rows;
+
+ /**
+ * The width of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t width = 0;
+
+ /**
+ * The height of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t height = 0;
+
+ /**
+ * Buffer to the raw image data stored sequentially.
+ * Use `rows` to access the data on a row-by-row basis.
+ */
+ std::unique_ptr<uint8_t[]> data;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Image);
+};
+
+/**
+ * A range of pixel values, starting at 'start' and ending before 'end'
+ * exclusive. Or rather [a, b).
+ */
+struct Range {
+ int32_t start = 0;
+ int32_t end = 0;
+
+ explicit Range() = default;
+ inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
+ }
+};
+
+inline bool operator==(const Range& left, const Range& right) {
+ return left.start == right.start && left.end == right.end;
+}
+
+/**
+ * Inset lengths from all edges of a rectangle. `left` and `top` are measured
+ * from the left and top
+ * edges, while `right` and `bottom` are measured from the right and bottom
+ * edges, respectively.
+ */
+struct Bounds {
+ int32_t left = 0;
+ int32_t top = 0;
+ int32_t right = 0;
+ int32_t bottom = 0;
+
+ explicit Bounds() = default;
+ inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
+ : left(l), top(t), right(r), bottom(b) {
+ }
+
+ bool nonZero() const;
+};
+
+inline bool Bounds::nonZero() const {
+ return left != 0 || top != 0 || right != 0 || bottom != 0;
+}
+
+inline bool operator==(const Bounds& left, const Bounds& right) {
+ return left.left == right.left && left.top == right.top && left.right == right.right &&
+ left.bottom == right.bottom;
+}
+
+/**
+ * Contains 9-patch data from a source image. All measurements exclude the 1px
+ * border of the
+ * source 9-patch image.
+ */
+class NinePatch {
+ public:
+ static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width,
+ const int32_t height, std::string* err_out);
+
+ /**
+ * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
+ * with format 0xAARRGGBB (the way 9-patch expects it).
+ */
+ static uint32_t PackRGBA(const uint8_t* pixel);
+
+ /**
+ * 9-patch content padding/insets. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ Bounds padding;
+
+ /**
+ * Optical layout bounds/insets. This overrides the padding for
+ * layout purposes. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ * See
+ * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
+ */
+ Bounds layout_bounds;
+
+ /**
+ * Outline of the image, calculated based on opacity.
+ */
+ Bounds outline;
+
+ /**
+ * The computed radius of the outline. If non-zero, the outline is a
+ * rounded-rect.
+ */
+ float outline_radius = 0.0f;
+
+ /**
+ * The largest alpha value within the outline.
+ */
+ uint32_t outline_alpha = 0x000000ffu;
+
+ /**
+ * Horizontal regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> horizontal_stretch_regions;
+
+ /**
+ * Vertical regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> vertical_stretch_regions;
+
+ /**
+ * The colors within each region, fixed or stretchable.
+ * For w*h regions, the color of region (x,y) is addressable
+ * via index y*w + x.
+ */
+ std::vector<uint32_t> region_colors;
+
+ /**
+ * Returns serialized data containing the original basic 9-patch meta data.
+ * Optical layout bounds and round rect outline data must be serialized
+ * separately using SerializeOpticalLayoutBounds() and
+ * SerializeRoundedRectOutline().
+ */
+ std::unique_ptr<uint8_t[]> SerializeBase(size_t* out_len) const;
+
+ /**
+ * Serializes the layout bounds.
+ */
+ std::unique_ptr<uint8_t[]> SerializeLayoutBounds(size_t* out_len) const;
+
+ /**
+ * Serializes the rounded-rect outline.
+ */
+ std::unique_ptr<uint8_t[]> SerializeRoundedRectOutline(size_t* out_len) const;
+
+ private:
+ explicit NinePatch() = default;
+
+ DISALLOW_COPY_AND_ASSIGN(NinePatch);
+};
+
+::std::ostream& operator<<(::std::ostream& out, const Range& range);
+::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
+::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch);
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 4d12885..3a72871 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -96,6 +96,9 @@
// The apk assets is owned by the application running in this process and incremental crash
// protections for this APK must be disabled.
PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1U << 4U,
+
+ // The apk assets only contain the overlayable declarations information.
+ PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
};
struct OverlayableInfo {
diff --git a/libs/androidfw/include/androidfw/Png.h b/libs/androidfw/include/androidfw/Png.h
new file mode 100644
index 0000000..2ece43e
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Png.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "BigBuffer.h"
+#include "IDiagnostics.h"
+#include "Image.h"
+#include "Source.h"
+#include "Streams.h"
+#include "android-base/macros.h"
+
+namespace android {
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
+struct PngOptions {
+ int grayscale_tolerance = 0;
+};
+
+/**
+ * Deprecated. Removing once new PNG crunching code is proved to be correct.
+ */
+class Png {
+ public:
+ explicit Png(IDiagnostics* diag) : mDiag(diag) {
+ }
+
+ bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Png);
+
+ IDiagnostics* mDiag;
+};
+
+/**
+ * An InputStream that filters out unimportant PNG chunks.
+ */
+class PngChunkFilter : public InputStream {
+ public:
+ explicit PngChunkFilter(StringPiece data);
+ virtual ~PngChunkFilter() = default;
+
+ bool Next(const void** buffer, size_t* len) override;
+ void BackUp(size_t count) override;
+
+ bool CanRewind() const override {
+ return true;
+ }
+ bool Rewind() override;
+ size_t ByteCount() const override {
+ return window_start_;
+ }
+
+ bool HadError() const override {
+ return !error_msg_.empty();
+ }
+ std::string GetError() const override {
+ return error_msg_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+
+ bool ConsumeWindow(const void** buffer, size_t* len);
+
+ StringPiece data_;
+ size_t window_start_ = 0;
+ size_t window_end_ = 0;
+ std::string error_msg_;
+};
+/**
+ * Reads a PNG from the InputStream into memory as an RGBA Image.
+ */
+std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag);
+
+/**
+ * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
+ * as a PNG.
+ */
+bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
+ const PngOptions& options, IDiagnostics* diag, bool verbose);
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 631bda4..c0514fd 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,6 +1375,8 @@
// match the requested configuration at all.
bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+ bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
+
String8 toString() const;
};
@@ -1870,7 +1872,10 @@
DataValue data_value;
std::string data_string_value;
std::optional<android::base::borrowed_fd> data_binary_value;
+ off64_t binary_data_offset;
+ size_t binary_data_size;
std::string configuration;
+ bool nine_patch;
};
class AssetManager2;
@@ -2162,6 +2167,7 @@
static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue);
static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue);
+ static bool stringToDouble(const char16_t* s, size_t len, double& outValue);
// Used with stringToValue.
class Accessor
diff --git a/libs/androidfw/include/androidfw/Streams.h b/libs/androidfw/include/androidfw/Streams.h
new file mode 100644
index 0000000..2daf0e2
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Streams.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <string>
+#include "android-base/off64_t.h"
+
+namespace android {
+
+// InputStream interface that mimics protobuf's ZeroCopyInputStream,
+// with added error handling methods to better report issues.
+class InputStream {
+ public:
+ virtual ~InputStream() = default;
+
+ // Returns a chunk of data for reading. data and size must not be nullptr.
+ // Returns true so long as there is more data to read, returns false if an error occurred
+ // or no data remains. If an error occurred, check HadError().
+ // The stream owns the buffer returned from this method and the buffer is invalidated
+ // anytime another mutable method is called.
+ virtual bool Next(const void** data, size_t* size) = 0;
+
+ // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+ // from Next().
+ // Useful when the last block returned from Next() wasn't fully read.
+ virtual void BackUp(size_t count) = 0;
+
+ // Returns true if this InputStream can rewind. If so, Rewind() can be called.
+ virtual bool CanRewind() const {
+ return false;
+ };
+
+ // Rewinds the stream to the beginning so it can be read again.
+ // Returns true if the rewind succeeded.
+ // This does nothing if CanRewind() returns false.
+ virtual bool Rewind() {
+ return false;
+ }
+
+ // Returns the number of bytes that have been read from the stream.
+ virtual size_t ByteCount() const = 0;
+
+ // Returns an error message if HadError() returned true.
+ virtual std::string GetError() const {
+ return {};
+ }
+
+ // Returns true if an error occurred. Errors are permanent.
+ virtual bool HadError() const = 0;
+
+ virtual bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
+ (void)data;
+ (void)byte_count;
+ (void)offset;
+ return false;
+ }
+};
+
+// A sub-InputStream interface that knows the total size of its stream.
+class KnownSizeInputStream : public InputStream {
+ public:
+ virtual size_t TotalSize() const = 0;
+};
+
+// OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
+// with added error handling methods to better report issues.
+class OutputStream {
+ public:
+ virtual ~OutputStream() = default;
+
+ // Returns a buffer to which data can be written to. The data written to this buffer will
+ // eventually be written to the stream. Call BackUp() if the data written doesn't occupy the
+ // entire buffer.
+ // Return false if there was an error.
+ // The stream owns the buffer returned from this method and the buffer is invalidated
+ // anytime another mutable method is called.
+ virtual bool Next(void** data, size_t* size) = 0;
+
+ // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+ // from Next().
+ // Useful for when the last block returned from Next() wasn't fully written to.
+ virtual void BackUp(size_t count) = 0;
+
+ // Returns the number of bytes that have been written to the stream.
+ virtual size_t ByteCount() const = 0;
+
+ // Returns an error message if HadError() returned true.
+ virtual std::string GetError() const {
+ return {};
+ }
+
+ // Returns true if an error occurred. Errors are permanent.
+ virtual bool HadError() const = 0;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 10f6d06..be1f98f 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -37,6 +37,8 @@
#include <unistd.h>
#include <time.h>
+#include <android-base/expected.h>
+
#include <util/map_ptr.h>
#include <utils/Compat.h>
@@ -102,6 +104,11 @@
*/
bool startIteration(void** cookie);
bool startIteration(void** cookie, const char* prefix, const char* suffix);
+ /*
+ * Same as above, but returns the error code in case of failure.
+ * #see libziparchive/zip_error.h.
+ */
+ base::expected<void*, int32_t> startIterationOrError(const char* prefix, const char* suffix);
/**
* Return the next entry in iteration order, or NULL if there are no more
@@ -109,6 +116,12 @@
*/
ZipEntryRO nextEntry(void* cookie);
+ /**
+ * Same as above, but returns the error code in case of failure.
+ * #see libziparchive/zip_error.h.
+ */
+ base::expected<ZipEntryRO, int32_t> nextEntryOrError(void* cookie);
+
void endIteration(void* cookie);
void releaseEntry(ZipEntryRO entry) const;
diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp
index 62e88c6..ac1dc9b 100644
--- a/libs/androidfw/tests/ApkParsing_test.cpp
+++ b/libs/androidfw/tests/ApkParsing_test.cpp
@@ -74,4 +74,10 @@
auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
ASSERT_THAT(lastSlash, IsNull());
}
+
+TEST(ApkParsingTest, InvalidPrefix) {
+ const char* path = "assets/libhello.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
}
\ No newline at end of file
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 6fae72a..2caa98c 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,10 +228,12 @@
ResTable_config config;
memset(&config, 0, sizeof(config));
+ std::vector<ResTable_config> configs;
+ configs.push_back(config);
while (state.KeepRunning()) {
- config.sdkVersion = ~config.sdkVersion;
- assets.SetConfiguration(config);
+ configs[0].sdkVersion = ~configs[0].sdkVersion;
+ assets.SetConfigurations(configs);
}
}
BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index df3fa02..c62f095 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
TEST_F(AssetManager2Test, DensityOverride) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
- assetmanager.SetConfiguration({
+ assetmanager.SetConfigurations({{
.density = ResTable_config::DENSITY_XHIGH,
.sdkVersion = 21,
- });
+ }});
auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
assetmanager.SetResourceResolutionLoggingEnabled(false);
@@ -736,7 +736,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({overlayable_assets_});
const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index b97dd96..8b883f4 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
AssetManager2 assetmanager;
assetmanager.SetApkAssets(apk_assets);
if (config != nullptr) {
- assetmanager.SetConfiguration(*config);
+ assetmanager.SetConfigurations({*config});
}
while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/FileStream_test.cpp b/libs/androidfw/tests/FileStream_test.cpp
new file mode 100644
index 0000000..9785975
--- /dev/null
+++ b/libs/androidfw/tests/FileStream_test.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "androidfw/FileStream.h"
+#include "androidfw/StringPiece.h"
+
+#include "android-base/file.h"
+#include "android-base/macros.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace android {
+
+TEST(FileInputStreamTest, NextAndBackup) {
+ std::string input = "this is a cool string";
+ TemporaryFile file;
+ ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21));
+ lseek64(file.fd, 0, SEEK_SET);
+
+ // Use a small buffer size so that we can call Next() a few times.
+ FileInputStream in(file.release(), 10u);
+ ASSERT_FALSE(in.HadError());
+ EXPECT_THAT(in.ByteCount(), Eq(0u));
+
+ const void* buffer;
+ size_t size;
+ ASSERT_TRUE(in.Next(&buffer, &size)) << in.GetError();
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(in.ByteCount(), Eq(10u));
+ EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("this is a "));
+
+ ASSERT_TRUE(in.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(in.ByteCount(), Eq(20u));
+ EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("cool strin"));
+
+ in.BackUp(5u);
+ EXPECT_THAT(in.ByteCount(), Eq(15u));
+
+ ASSERT_TRUE(in.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(5u));
+ ASSERT_THAT(buffer, NotNull());
+ ASSERT_THAT(in.ByteCount(), Eq(20u));
+ EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("strin"));
+
+ // Backup 1 more than possible. Should clamp.
+ in.BackUp(11u);
+ EXPECT_THAT(in.ByteCount(), Eq(10u));
+
+ ASSERT_TRUE(in.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ ASSERT_THAT(in.ByteCount(), Eq(20u));
+ EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("cool strin"));
+
+ ASSERT_TRUE(in.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(1u));
+ ASSERT_THAT(buffer, NotNull());
+ ASSERT_THAT(in.ByteCount(), Eq(21u));
+ EXPECT_THAT(StringPiece(reinterpret_cast<const char*>(buffer), size), Eq("g"));
+
+ EXPECT_FALSE(in.Next(&buffer, &size));
+ EXPECT_FALSE(in.HadError());
+}
+
+TEST(FileOutputStreamTest, NextAndBackup) {
+ const std::string input = "this is a cool string";
+
+ TemporaryFile file;
+
+ FileOutputStream out(file.fd, 10u);
+ ASSERT_FALSE(out.HadError());
+ EXPECT_THAT(out.ByteCount(), Eq(0u));
+
+ void* buffer;
+ size_t size;
+ ASSERT_TRUE(out.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(out.ByteCount(), Eq(10u));
+ memcpy(reinterpret_cast<char*>(buffer), input.c_str(), size);
+
+ ASSERT_TRUE(out.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(out.ByteCount(), Eq(20u));
+ memcpy(reinterpret_cast<char*>(buffer), input.c_str() + 10u, size);
+
+ ASSERT_TRUE(out.Next(&buffer, &size));
+ ASSERT_THAT(size, Eq(10u));
+ ASSERT_THAT(buffer, NotNull());
+ EXPECT_THAT(out.ByteCount(), Eq(30u));
+ reinterpret_cast<char*>(buffer)[0] = input[20u];
+ out.BackUp(size - 1);
+ EXPECT_THAT(out.ByteCount(), Eq(21u));
+
+ ASSERT_TRUE(out.Flush());
+
+ lseek64(file.fd, 0, SEEK_SET);
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(file.fd, &actual));
+ EXPECT_THAT(actual, StrEq(input));
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/Generic_bench.cpp b/libs/androidfw/tests/Generic_bench.cpp
new file mode 100644
index 0000000..4c978e8
--- /dev/null
+++ b/libs/androidfw/tests/Generic_bench.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <stdint.h>
+
+#include <map>
+#include <unordered_map>
+
+#include "benchmark/benchmark.h"
+
+namespace android {
+
+template <class Map = std::unordered_map<uint32_t, std::vector<uint32_t>>>
+static Map prepare_map() {
+ Map map;
+ std::vector<uint32_t> vec;
+ for (int i = 0; i < 1000; ++i) {
+ map.emplace(i, vec);
+ }
+ return map;
+}
+
+static void BM_hashmap_emplace_same(benchmark::State& state) {
+ auto map = prepare_map<>();
+ auto val = map.size() - 1;
+ std::vector<uint32_t> vec;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.emplace(val, vec));
+ }
+}
+BENCHMARK(BM_hashmap_emplace_same);
+static void BM_hashmap_try_emplace_same(benchmark::State& state) {
+ auto map = prepare_map();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.try_emplace(val));
+ }
+}
+BENCHMARK(BM_hashmap_try_emplace_same);
+static void BM_hashmap_find(benchmark::State& state) {
+ auto map = prepare_map<>();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.find(val));
+ }
+}
+BENCHMARK(BM_hashmap_find);
+
+static void BM_hashmap_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map<>();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.emplace(i++, vec);
+ }
+}
+BENCHMARK(BM_hashmap_emplace_diff);
+static void BM_hashmap_try_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map();
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.try_emplace(i++);
+ }
+}
+BENCHMARK(BM_hashmap_try_emplace_diff);
+static void BM_hashmap_find_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map<>();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ if (map.find(i++) == map.end()) {
+ map.emplace(i - 1, vec);
+ }
+ }
+}
+BENCHMARK(BM_hashmap_find_emplace_diff);
+
+static void BM_treemap_emplace_same(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ auto val = map.size() - 1;
+ std::vector<uint32_t> vec;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.emplace(val, vec));
+ }
+}
+BENCHMARK(BM_treemap_emplace_same);
+static void BM_treemap_try_emplace_same(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.try_emplace(val));
+ }
+}
+BENCHMARK(BM_treemap_try_emplace_same);
+static void BM_treemap_find(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.find(val));
+ }
+}
+BENCHMARK(BM_treemap_find);
+
+static void BM_treemap_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.emplace(i++, vec);
+ }
+}
+BENCHMARK(BM_treemap_emplace_diff);
+static void BM_treemap_try_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map();
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.try_emplace(i++);
+ }
+}
+BENCHMARK(BM_treemap_try_emplace_diff);
+static void BM_treemap_find_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ if (map.find(i++) == map.end()) {
+ map.emplace(i - 1, vec);
+ }
+ }
+}
+BENCHMARK(BM_treemap_find_emplace_diff);
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/tests/NinePatch_test.cpp b/libs/androidfw/tests/NinePatch_test.cpp
new file mode 100644
index 0000000..7ee8e9e
--- /dev/null
+++ b/libs/androidfw/tests/NinePatch_test.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "androidfw/Image.h"
+#include "androidfw/ResourceTypes.h"
+#include "gtest/gtest.h"
+
+namespace android {
+
+// Pixels are in RGBA_8888 packing.
+
+#define RED "\xff\x00\x00\xff"
+#define BLUE "\x00\x00\xff\xff"
+#define GREEN "\xff\x00\x00\xff"
+#define GR_70 "\xff\x00\x00\xb3"
+#define GR_50 "\xff\x00\x00\x80"
+#define GR_20 "\xff\x00\x00\x33"
+#define BLACK "\x00\x00\x00\xff"
+#define WHITE "\xff\xff\xff\xff"
+#define TRANS "\x00\x00\x00\x00"
+
+static uint8_t* k2x2[] = {
+ (uint8_t*)WHITE WHITE,
+ (uint8_t*)WHITE WHITE,
+};
+
+static uint8_t* kMixedNeutralColor3x3[] = {
+ (uint8_t*)WHITE BLACK TRANS,
+ (uint8_t*)TRANS RED TRANS,
+ (uint8_t*)WHITE WHITE WHITE,
+};
+
+static uint8_t* kTransparentNeutralColor3x3[] = {
+ (uint8_t*)TRANS BLACK TRANS,
+ (uint8_t*)BLACK RED BLACK,
+ (uint8_t*)TRANS BLACK TRANS,
+};
+
+static uint8_t* kSingleStretch7x6[] = {
+ (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kMultipleStretch10x7[] = {
+ (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kPadding6x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
+};
+
+static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
+ (uint8_t*)WHITE RED WHITE,
+ (uint8_t*)RED WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE,
+};
+
+static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE RED WHITE WHITE,
+};
+
+static uint8_t* kLayoutBounds5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED WHITE RED WHITE,
+};
+
+static uint8_t* kAsymmetricLayoutBounds5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE WHITE WHITE,
+};
+
+static uint8_t* kPaddingAndLayoutBounds5x5[] = {
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED BLACK RED WHITE,
+};
+
+static uint8_t* kColorfulImage5x5[] = {
+ (uint8_t*)WHITE BLACK WHITE BLACK WHITE, (uint8_t*)BLACK RED BLUE GREEN WHITE,
+ (uint8_t*)BLACK RED GREEN GREEN WHITE, (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineOpaque10x10[] = {
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineTranslucent10x10[] = {
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineOffsetTranslucent12x10[] = {
+ (uint8_t*)WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kOutlineRadius5x5[] = {
+ (uint8_t*)WHITE BLACK BLACK BLACK WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)BLACK GREEN GREEN GREEN WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+};
+
+static uint8_t* kStretchAndPadding5x5[] = {
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE,
+};
+
+TEST(NinePatchTest, Minimum3x3) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(k2x2, 2, 2, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, MixedNeutralColors) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(kMixedNeutralColor3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, TransparentNeutralColor) {
+ std::string err;
+ EXPECT_NE(nullptr, NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
+}
+
+TEST(NinePatchTest, SingleStretchRegion) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
+ ASSERT_EQ(1u, nine_patch->vertical_stretch_regions.size());
+
+ EXPECT_EQ(Range(1, 4), nine_patch->horizontal_stretch_regions.front());
+ EXPECT_EQ(Range(1, 3), nine_patch->vertical_stretch_regions.front());
+}
+
+TEST(NinePatchTest, MultipleStretchRegions) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
+ ASSERT_EQ(2u, nine_patch->vertical_stretch_regions.size());
+
+ EXPECT_EQ(Range(1, 2), nine_patch->horizontal_stretch_regions[0]);
+ EXPECT_EQ(Range(3, 5), nine_patch->horizontal_stretch_regions[1]);
+ EXPECT_EQ(Range(6, 7), nine_patch->horizontal_stretch_regions[2]);
+
+ EXPECT_EQ(Range(0, 2), nine_patch->vertical_stretch_regions[0]);
+ EXPECT_EQ(Range(3, 5), nine_patch->vertical_stretch_regions[1]);
+}
+
+TEST(NinePatchTest, InferPaddingFromStretchRegions) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding);
+}
+
+TEST(NinePatchTest, Padding) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPadding6x5, 6, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+}
+
+TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+ EXPECT_FALSE(err.empty());
+}
+
+TEST(NinePatchTest, LayoutBounds) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+
+ nine_patch = NinePatch::Create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 0, 0), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, PaddingAndLayoutBounds) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
+}
+
+TEST(NinePatchTest, RegionColorsAreCorrect) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ std::vector<uint32_t> expected_colors = {
+ NinePatch::PackRGBA((uint8_t*)RED), (uint32_t)android::Res_png_9patch::NO_COLOR,
+ NinePatch::PackRGBA((uint8_t*)GREEN), (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+ NinePatch::PackRGBA((uint8_t*)BLUE), NinePatch::PackRGBA((uint8_t*)GREEN),
+ };
+ EXPECT_EQ(expected_colors, nine_patch->region_colors);
+}
+
+TEST(NinePatchTest, OutlineFromOpaqueImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
+ EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromTranslucentImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
+ EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineFromOffCenterImage) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the
+ // middle for each inset. If the outline is shifted, the search may not find a
+ // closer bounds.
+ // This check should be:
+ // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
+ // but until I know what behavior I'm breaking, I will leave it at the
+ // incorrect:
+ EXPECT_EQ(Bounds(4, 3, 3, 3), nine_patch->outline);
+
+ EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
+ EXPECT_EQ(0.0f, nine_patch->outline_radius);
+}
+
+TEST(NinePatchTest, OutlineRadius) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+ EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
+ EXPECT_EQ(3.4142f, nine_patch->outline_radius);
+}
+
+::testing::AssertionResult BigEndianOne(uint8_t* cursor) {
+ if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure() << "Not BigEndian 1";
+}
+
+TEST(NinePatchTest, SerializePngEndianness) {
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, nine_patch);
+
+ size_t len;
+ std::unique_ptr<uint8_t[]> data = nine_patch->SerializeBase(&len);
+ ASSERT_NE(nullptr, data);
+ ASSERT_NE(0u, len);
+
+ // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset +
+ // yDivsOffset
+ // (12 bytes)
+ uint8_t* cursor = data.get() + 12;
+
+ // Check that padding is big-endian. Expecting value 1.
+ EXPECT_TRUE(BigEndianOne(cursor));
+ EXPECT_TRUE(BigEndianOne(cursor + 4));
+ EXPECT_TRUE(BigEndianOne(cursor + 8));
+ EXPECT_TRUE(BigEndianOne(cursor + 12));
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index e08a6a7..181d141 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
ResTable_config night{};
night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
night.version = 8u;
- am_night.SetConfiguration(night);
+ am_night.SetConfigurations({night});
auto theme = am.NewTheme();
{
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e0ccf17..14e8653 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -28,6 +28,19 @@
],
}
+aconfig_declarations {
+ name: "hwui_flags",
+ package: "com.android.graphics.hwui.flags",
+ srcs: [
+ "aconfig/hwui_flags.aconfig",
+ ],
+}
+
+cc_aconfig_library {
+ name: "hwui_flags_cc_lib",
+ aconfig_declarations: "hwui_flags",
+}
+
cc_defaults {
name: "hwui_defaults",
defaults: [
@@ -44,7 +57,7 @@
"-DEGL_EGLEXT_PROTOTYPES",
"-DGL_GLEXT_PROTOTYPES",
"-DATRACE_TAG=ATRACE_TAG_VIEW",
- "-DLOG_TAG=\"OpenGLRenderer\"",
+ "-DLOG_TAG=\"HWUI\"",
"-Wall",
"-Wthread-safety",
"-Wno-unused-parameter",
@@ -122,6 +135,8 @@
"libcrypto",
"libsync",
"libui",
+ "aconfig_text_flags_c_lib",
+ "server_configurable_flags",
],
static_libs: [
"libEGL_blobCache",
@@ -131,6 +146,7 @@
"libstatspull_lazy",
"libstatssocket_lazy",
"libtonemap",
+ "hwui_flags_cc_lib",
],
},
host: {
@@ -520,6 +536,7 @@
"utils/Blur.cpp",
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
+ "utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
"Animator.cpp",
@@ -681,11 +698,13 @@
],
static_libs: [
+ "libflagtest",
"libgmock",
"libhwui_static",
],
shared_libs: [
"libmemunreachable",
+ "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
@@ -725,6 +744,7 @@
"tests/unit/TestUtilsTests.cpp",
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
+ "tests/unit/UnderlineTest.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index b656b6a..4d020c5 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -16,6 +16,11 @@
#include "AutoBackendTextureRelease.h"
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrBackendSurface.h>
+#include <include/gpu/MutableTextureState.h>
#include "renderthread/RenderThread.h"
#include "utils/Color.h"
#include "utils/PaintUtils.h"
@@ -30,15 +35,47 @@
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
- GrBackendFormat backendFormat =
- GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+
+ GrBackendFormat backendFormat;
+ GrBackendApi backend = context->backend();
+ if (backend == GrBackendApi::kOpenGL) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ false);
+ } else if (backend == GrBackendApi::kVulkan) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+ buffer,
+ desc.format,
+ false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ false);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+ }
LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
__FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
", AHardwareBuffer_Format==%" PRIu32 ".",
static_cast<int>(context->backend()), desc.format);
- mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
- context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
- createProtectedImage, backendFormat, false);
LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
__FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
", protected==%d",
@@ -70,7 +107,7 @@
// releaseProc is invoked by SkImage, when texture is no longer in use.
// "releaseContext" contains an "AutoBackendTextureRelease*".
-static void releaseProc(SkImage::ReleaseContext releaseContext) {
+static void releaseProc(SkImages::ReleaseContext releaseContext) {
AutoBackendTextureRelease* textureRelease =
reinterpret_cast<AutoBackendTextureRelease*>(releaseContext);
textureRelease->unref(false);
@@ -83,10 +120,10 @@
AHardwareBuffer_describe(buffer, &desc);
SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
// The following ref will be counteracted by Skia calling releaseProc, either during
- // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
- // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+ // BorrowTextureFrom if there is a failure, or later when SkImage is discarded. It must
+ // be called before BorrowTextureFrom, otherwise Skia may remove HWUI's ref on failure.
ref();
- mImage = SkImage::MakeFromTexture(
+ mImage = SkImages::BorrowTextureFrom(
context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
}
@@ -105,8 +142,8 @@
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
- GrBackendSurfaceMutableState newState(VK_IMAGE_LAYOUT_UNDEFINED,
- VK_QUEUE_FAMILY_FOREIGN_EXT);
+ skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_QUEUE_FAMILY_FOREIGN_EXT);
// The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
// releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index cd4fae8..b667daf 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -80,6 +80,19 @@
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
if (transform == ColorTransform::None) return;
+ if (transform == ColorTransform::Invert) {
+ auto filter = SkHighContrastFilter::Make(
+ {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
+ /* contrast= */ 0.0f});
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+ } else {
+ paint.setColorFilter(filter);
+ }
+ return;
+ }
+
SkColor newColor = transformColor(transform, paint.getColor());
paint.setColor(newColor);
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index 291f4cf..288dca4 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -29,12 +29,15 @@
Unknown = 0,
Background = 1,
Foreground = 2,
+ // Contains foreground (usually text), like a button or chip
+ Container = 3
};
enum class ColorTransform {
None,
Light,
Dark,
+ Invert
};
// True if the paint was modified, false otherwise
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
new file mode 100644
index 0000000..1a5b938
--- /dev/null
+++ b/libs/hwui/ColorFilter.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef COLORFILTER_H_
+#define COLORFILTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "GraphicsJNI.h"
+#include "SkColorFilter.h"
+#include "SkiaWrapper.h"
+
+namespace android {
+namespace uirenderer {
+
+class ColorFilter : public SkiaWrapper<SkColorFilter> {
+public:
+ static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
+
+protected:
+ ColorFilter() = default;
+};
+
+class BlendModeColorFilter : public ColorFilter {
+public:
+ BlendModeColorFilter(SkColor color, SkBlendMode mode) : mColor(color), mMode(mode) {}
+
+private:
+ sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Blend(mColor, mMode); }
+
+private:
+ const SkColor mColor;
+ const SkBlendMode mMode;
+};
+
+class LightingFilter : public ColorFilter {
+public:
+ LightingFilter(SkColor mul, SkColor add) : mMul(mul), mAdd(add) {}
+
+ void setMul(SkColor mul) {
+ mMul = mul;
+ discardInstance();
+ }
+
+ void setAdd(SkColor add) {
+ mAdd = add;
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Lighting(mMul, mAdd); }
+
+private:
+ SkColor mMul;
+ SkColor mAdd;
+};
+
+class ColorMatrixColorFilter : public ColorFilter {
+public:
+ ColorMatrixColorFilter(std::vector<float>&& matrix) : mMatrix(std::move(matrix)) {}
+
+ void setMatrix(std::vector<float>&& matrix) {
+ mMatrix = std::move(matrix);
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override {
+ return SkColorFilters::Matrix(mMatrix.data());
+ }
+
+private:
+ std::vector<float> mMatrix;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // COLORFILTER_H_
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index a8d170d..fd27641 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -242,6 +242,47 @@
}
}
+SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const {
+ const DirtyStack* frame = mHead;
+ Matrix4 transform;
+ SkRect pretransformResult = bounds;
+ while (true) {
+ SkRect currentBounds = pretransformResult;
+ pretransformResult.setEmpty();
+ switch (frame->type) {
+ case TransformRenderNode: {
+ const RenderProperties& props = frame->renderNode->properties();
+ // Perform clipping
+ if (props.getClipDamageToBounds() && !currentBounds.isEmpty()) {
+ if (!currentBounds.intersect(
+ SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
+ currentBounds.setEmpty();
+ }
+ }
+
+ // apply all transforms
+ mapRect(props, currentBounds, &pretransformResult);
+ frame->renderNode->applyViewPropertyTransforms(transform);
+ } break;
+ case TransformMatrix4:
+ mapRect(frame->matrix4, currentBounds, &pretransformResult);
+ transform.multiply(*frame->matrix4);
+ break;
+ default:
+ pretransformResult = currentBounds;
+ break;
+ }
+ if (frame->prev == frame) break;
+ frame = frame->prev;
+ }
+ SkRect result;
+ Matrix4 globalToLocal;
+ globalToLocal.loadInverse(transform);
+ mapRect(&globalToLocal, pretransformResult, &result);
+ *outMatrix = transform;
+ return result;
+}
+
void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
mHead->pendingDirty.join({left, top, right, bottom});
}
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index c4249af..30bf706 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -61,6 +61,8 @@
void computeCurrentTransform(Matrix4* outMatrix) const;
+ SkRect computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const;
+
void finish(SkRect* totalDirty);
struct StretchResult {
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index eb5878d..b1c5bf4 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -54,6 +54,8 @@
mImpl->updateChildren(std::move(updateFn));
}
+ void visit(std::function<void(const RenderNode&)> func) const { mImpl->visit(std::move(func)); }
+
[[nodiscard]] explicit operator bool() const {
return mImpl.get() != nullptr;
}
@@ -143,6 +145,8 @@
return mImpl && mImpl->hasText();
}
+ [[nodiscard]] bool hasFill() const { return mImpl && mImpl->hasFill(); }
+
void applyColorTransform(ColorTransform transform) {
if (mImpl) {
mImpl->applyColorTransform(transform);
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index a18ba1c..d21f07ef 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 0000000..00d049c
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef ANDROID_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+ return com_android_text_flags_fix_double_underline();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+inline bool deprecate_ui_fonts() {
+#ifdef __ANDROID__
+ return com_android_text_flags_deprecate_ui_fonts();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+} // namespace text_feature
+
+} // namespace android
+
+#endif // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 8191f5e..a958a09 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -15,6 +15,8 @@
*/
#include "FrameInfo.h"
+#include <gui/TraceUtils.h>
+
#include <cstring>
namespace android {
@@ -51,6 +53,30 @@
void FrameInfo::importUiThreadInfo(int64_t* info) {
memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+ mSkippedFrameReason.reset();
+}
+
+const char* toString(SkippedFrameReason reason) {
+ switch (reason) {
+ case SkippedFrameReason::DrawingOff:
+ return "DrawingOff";
+ case SkippedFrameReason::ContextIsStopped:
+ return "ContextIsStopped";
+ case SkippedFrameReason::NothingToDraw:
+ return "NothingToDraw";
+ case SkippedFrameReason::NoOutputTarget:
+ return "NoOutputTarget";
+ case SkippedFrameReason::NoBuffer:
+ return "NoBuffer";
+ case SkippedFrameReason::AlreadyDrawn:
+ return "AlreadyDrawn";
+ }
+}
+
+void FrameInfo::setSkippedFrameReason(android::uirenderer::SkippedFrameReason reason) {
+ ATRACE_FORMAT_INSTANT("Frame skipped: %s", toString(reason));
+ addFlag(FrameInfoFlags::SkippedFrame);
+ mSkippedFrameReason = reason;
}
} /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index b15b6cb..f7ad139 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -16,15 +16,17 @@
#ifndef FRAMEINFO_H_
#define FRAMEINFO_H_
-#include "utils/Macros.h"
-
#include <cutils/compiler.h>
+#include <memory.h>
#include <utils/Timers.h>
#include <array>
-#include <memory.h>
+#include <optional>
#include <string>
+#include "SkippedFrameInfo.h"
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
@@ -186,8 +188,14 @@
return mFrameInfo[static_cast<int>(index)];
}
+ void setSkippedFrameReason(SkippedFrameReason reason);
+ inline std::optional<SkippedFrameReason> getSkippedFrameReason() const {
+ return mSkippedFrameReason;
+ }
+
private:
int64_t mFrameInfo[static_cast<int>(FrameInfoIndex::NumIndexes)];
+ std::optional<SkippedFrameReason> mSkippedFrameReason;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 687e4dd..59f2169 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -148,7 +148,7 @@
int fast_i = 0, janky_i = 0;
// Set the bottom of all the shapes to the baseline
for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) {
- if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+ if (mFrameSource[fi].getSkippedFrameReason()) {
continue;
}
float lineWidth = baseLineWidth;
@@ -181,7 +181,7 @@
int janky_i = (mNumJankyRects - 1) * 4;
for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
- if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+ if (mFrameSource[fi].getSkippedFrameReason()) {
continue;
}
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b7e9999..16de21d 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -22,9 +22,11 @@
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GrDirectContext.h>
+#include <GrTypes.h>
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
#include <gui/TraceUtils.h>
@@ -262,8 +264,9 @@
}
sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
- mGrContext->submit(true);
+ SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(),
+ ahb);
+ mGrContext->submit(GrSyncCpu::kYes);
uploadSucceeded = (image.get() != nullptr);
});
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
index ca1312e7..21f4ca7 100644
--- a/libs/hwui/MemoryPolicy.cpp
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -28,7 +28,10 @@
constexpr static MemoryPolicy sDefaultMemoryPolicy;
constexpr static MemoryPolicy sPersistentOrSystemPolicy{
.contextTimeout = 10_s,
+ .minimumResourceRetention = 1_s,
+ .maximumResourceRetention = 10_s,
.useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
};
constexpr static MemoryPolicy sLowRamPolicy{
.useAlternativeUiHidden = true,
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 347daf34..e10dda9 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,6 +53,8 @@
// The minimum amount of time to hold onto items in the resource cache
// The actual time used will be the max of this & when frames were actually rendered
nsecs_t minimumResourceRetention = 10_s;
+ // The maximum amount of time to hold onto items in the resource cache
+ nsecs_t maximumResourceRetention = 100000_s;
// If false, use only TRIM_UI_HIDDEN to drive background cache limits;
// If true, use all signals (such as all contexts are stopped) to drive the limits
bool useAlternativeUiHidden = true;
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 13e3c8e..69fda34 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -19,6 +19,7 @@
#include <GrDirectContext.h>
#include <SkMesh.h>
+#include <include/gpu/ganesh/SkMeshGanesh.h>
#include <jni.h>
#include <log/log.h>
@@ -143,21 +144,34 @@
}
if (mIsDirty || genId != mGenerationId) {
- auto vb = SkMesh::MakeVertexBuffer(
- context, reinterpret_cast<const void*>(mVertexBufferData.data()),
- mVertexBufferData.size());
+ auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
+#ifdef __ANDROID__
+ auto vb = SkMeshes::MakeVertexBuffer(context,
+ vertexData,
+ mVertexBufferData.size());
+#else
+ auto vb = SkMeshes::MakeVertexBuffer(vertexData,
+ mVertexBufferData.size());
+#endif
auto meshMode = SkMesh::Mode(mMode);
if (!mIndexBufferData.empty()) {
- auto ib = SkMesh::MakeIndexBuffer(
- context, reinterpret_cast<const void*>(mIndexBufferData.data()),
- mIndexBufferData.size());
+ auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
+#ifdef __ANDROID__
+ auto ib = SkMeshes::MakeIndexBuffer(context,
+ indexData,
+ mIndexBufferData.size());
+#else
+ auto ib = SkMeshes::MakeIndexBuffer(indexData,
+ mIndexBufferData.size());
+#endif
mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
- mBounds)
+ SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
.mesh;
} else {
mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- mBuilder->fUniforms, mBounds)
+ mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+ mBounds)
.mesh;
}
mIsDirty = false;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 09d3f05..6c3172a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -20,15 +20,26 @@
#ifdef __ANDROID__
#include "HWUIProperties.sysprop.h"
#endif
-#include "SkTraceEventCommon.h"
+#include <android-base/properties.h>
+#include <cutils/compiler.h>
+#include <log/log.h>
#include <algorithm>
#include <cstdlib>
#include <optional>
-#include <android-base/properties.h>
-#include <cutils/compiler.h>
-#include <log/log.h>
+#include "src/core/SkTraceEventCommon.h"
+
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool clip_surfaceviews() {
+ return false;
+}
+} // namespace hwui_flags
+#endif
namespace android {
namespace uirenderer {
@@ -92,6 +103,8 @@
float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
+bool Properties::clipSurfaceViews = false;
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -159,6 +172,9 @@
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
+ clipSurfaceViews =
+ base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bb47744..bca57e9 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -325,6 +325,8 @@
static float maxHdrHeadroomOn8bit;
+ static bool clipSurfaceViews;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 045de35..afe4c38 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -21,6 +21,7 @@
#include <SkCanvas.h>
#include <SkColorSpace.h>
#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkMatrix.h>
#include <SkPaint.h>
@@ -29,6 +30,7 @@
#include <SkSamplingOptions.h>
#include <SkSurface.h>
#include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <gui/TraceUtils.h>
#include <private/android/AHardwareBufferHelpers.h>
#include <shaders/shaders.h>
@@ -108,7 +110,8 @@
sk_sp<SkColorSpace> colorSpace =
DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
+ SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType,
+ colorSpace);
if (!image.get()) {
return request->onCopyFinished(CopyResult::UnknownError);
@@ -171,16 +174,16 @@
SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
SkBitmap* bitmap = &skBitmap;
sk_sp<SkSurface> tmpSurface =
- SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
- bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ SkSurfaces::RenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
+ bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
return request->onCopyFinished(CopyResult::UnknownError);
@@ -346,19 +349,19 @@
* a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
* software buffer.
*/
- sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- bitmap->info(),
- 0,
- kTopLeft_GrSurfaceOrigin, nullptr);
+ sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ bitmap->info(),
+ 0,
+ kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
return false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 924fbd6..3b694c5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
#include "SkImageFilter.h"
#include "SkImageInfo.h"
#include "SkLatticeIter.h"
+#include "SkMesh.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkRRect.h"
@@ -49,6 +50,7 @@
#include "effects/GainmapRenderer.h"
#include "include/gpu/GpuTypes.h" // from Skia
#include "include/gpu/GrDirectContext.h"
+#include "include/gpu/ganesh/SkMeshGanesh.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
#ifdef __ANDROID__
@@ -107,11 +109,6 @@
};
static_assert(sizeof(Op) == 4, "");
-struct Flush final : Op {
- static const auto kType = Type::Flush;
- void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); }
-};
-
struct Save final : Op {
static const auto kType = Type::Save;
void draw(SkCanvas* c, const SkMatrix&) const { c->save(); }
@@ -532,24 +529,26 @@
mutable bool isGpuBased;
mutable GrDirectContext::DirectContextID contextId;
void draw(SkCanvas* c, const SkMatrix&) const {
+#ifdef __ANDROID__
GrDirectContext* directContext = c->recordingContext()->asDirectContext();
GrDirectContext::DirectContextID id = directContext->directContextID();
if (!isGpuBased || contextId != id) {
sk_sp<SkMesh::VertexBuffer> vb =
- SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+ SkMeshes::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
if (!cpuMesh.indexBuffer()) {
gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
- cpuMesh.bounds())
+ SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
.mesh;
} else {
sk_sp<SkMesh::IndexBuffer> ib =
- SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+ SkMeshes::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
cpuMesh.indexCount(), cpuMesh.indexOffset(),
- cpuMesh.refUniforms(), cpuMesh.bounds())
+ cpuMesh.refUniforms(),
+ SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
.mesh;
}
@@ -558,6 +557,9 @@
}
c->drawMesh(gpuMesh, blender, paint);
+#else
+ c->drawMesh(cpuMesh, blender, paint);
+#endif
}
};
@@ -675,12 +677,11 @@
// because the webview functor still doesn't respect the canvas clip stack.
const SkIRect deviceBounds = c->getDeviceClipBounds();
if (mLayerSurface == nullptr || c->imageInfo() != mLayerImageInfo) {
- GrRecordingContext* directContext = c->recordingContext();
mLayerImageInfo =
c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height());
- mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes,
- mLayerImageInfo, 0,
- kTopLeft_GrSurfaceOrigin, nullptr);
+ // SkCanvas::makeSurface returns a new surface that will be GPU-backed if
+ // canvas was also.
+ mLayerSurface = c->makeSurface(mLayerImageInfo);
}
SkCanvas* layerCanvas = mLayerSurface->getCanvas();
@@ -717,6 +718,27 @@
return (value & (value - 1)) == 0;
}
+template <typename T>
+constexpr bool doesPaintHaveFill(T& paint) {
+ using T1 = std::remove_cv_t<T>;
+ if constexpr (std::is_same_v<T1, SkPaint>) {
+ return paint.getStyle() != SkPaint::Style::kStroke_Style;
+ } else if constexpr (std::is_same_v<T1, SkPaint&>) {
+ return paint.getStyle() != SkPaint::Style::kStroke_Style;
+ } else if constexpr (std::is_same_v<T1, SkPaint*>) {
+ return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+ } else if constexpr (std::is_same_v<T1, const SkPaint*>) {
+ return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+ }
+
+ return false;
+}
+
+template <typename... Args>
+constexpr bool hasPaintWithFill(Args&&... args) {
+ return (... || doesPaintHaveFill(args));
+}
+
template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
size_t skip = SkAlignPtr(sizeof(T) + pod);
@@ -735,6 +757,14 @@
new (op) T{std::forward<Args>(args)...};
op->type = (uint32_t)T::kType;
op->skip = skip;
+
+ // check if this is a fill op or not, in case we need to avoid messing with it with force invert
+ if constexpr (!std::is_same_v<T, DrawTextBlob>) {
+ if (hasPaintWithFill(args...)) {
+ mHasFill = true;
+ }
+ }
+
return op + 1;
}
@@ -752,10 +782,6 @@
}
}
-void DisplayListData::flush() {
- this->push<Flush>(0);
-}
-
void DisplayListData::save() {
this->push<Save>(0);
}
@@ -1047,10 +1073,6 @@
return nullptr;
}
-void RecordingCanvas::onFlush() {
- fDL->flush();
-}
-
void RecordingCanvas::willSave() {
mSaveCount++;
fDL->save();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1f4ba5d..afadbfd 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -110,7 +110,7 @@
class DisplayListData final {
public:
- DisplayListData() : mHasText(false) {}
+ DisplayListData() : mHasText(false), mHasFill(false) {}
~DisplayListData();
void draw(SkCanvas* canvas) const;
@@ -121,14 +121,13 @@
void applyColorTransform(ColorTransform transform);
bool hasText() const { return mHasText; }
+ bool hasFill() const { return mHasFill; }
size_t usedSize() const { return fUsed; }
size_t allocatedSize() const { return fReserved; }
private:
friend class RecordingCanvas;
- void flush();
-
void save();
void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags);
void saveBehind(const SkRect*);
@@ -194,6 +193,7 @@
size_t fReserved = 0;
bool mHasText : 1;
+ bool mHasFill : 1;
};
class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
@@ -208,8 +208,6 @@
void willRestore() override;
bool onDoSaveBehind(const SkRect*) override;
- void onFlush() override;
-
void didConcat44(const SkM44&) override;
void didSetM44(const SkM44&) override;
void didScale(SkScalar, SkScalar) override;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a733d17..f526a28 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -28,16 +28,21 @@
#include "DamageAccumulator.h"
#include "pipeline/skia/SkiaDisplayList.h"
#endif
-#include <gui/TraceUtils.h>
-#include "utils/MathUtils.h"
-#include "utils/StringUtils.h"
-
#include <SkPathOps.h>
+#include <gui/TraceUtils.h>
+#include <ui/FatVector.h>
+
#include <algorithm>
#include <atomic>
#include <sstream>
#include <string>
-#include <ui/FatVector.h>
+
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
+#include "utils/ForceDark.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
namespace android {
namespace uirenderer {
@@ -109,6 +114,13 @@
output << std::endl;
}
+void RenderNode::visit(std::function<void(const RenderNode&)> func) const {
+ func(*this);
+ if (mDisplayList) {
+ mDisplayList.visit(func);
+ }
+}
+
int RenderNode::getUsageSize() {
int size = sizeof(RenderNode);
size += mStagingDisplayList.getUsedSize();
@@ -363,13 +375,18 @@
mImageFilterClipBounds != clipBounds ||
mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
// Otherwise create a new snapshot with the given filter and snapshot
- mSnapshotResult.snapshot =
- snapshot->makeWithFilter(context,
- imageFilter,
- subset,
- clipBounds,
- &mSnapshotResult.outSubset,
- &mSnapshotResult.outOffset);
+#ifdef __ANDROID__
+ if (context) {
+ mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+ context, snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+ &mSnapshotResult.outOffset);
+ } else
+#endif
+ {
+ mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+ snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+ &mSnapshotResult.outOffset);
+ }
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
@@ -387,16 +404,21 @@
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
if (mDisplayList) {
- WebViewSyncData syncData {
- .applyForceDark = info && !info->disableForceDark
- };
+ WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
mDisplayList.syncContents(syncData);
handleForceDark(info);
}
}
+inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
+ return CC_UNLIKELY(
+ info &&
+ (!info->disableForceDark ||
+ info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK));
+}
+
void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
- if (CC_LIKELY(!info || info->disableForceDark)) {
+ if (!shouldEnableForceDark(info)) {
return;
}
auto usage = usageHint();
@@ -405,7 +427,13 @@
children.push_back(node);
});
if (mDisplayList.hasText()) {
- usage = UsageHint::Foreground;
+ if (mDisplayList.hasFill()) {
+ // Handle a special case for custom views that draw both text and background in the
+ // same RenderNode, which would otherwise be altered to white-on-white text.
+ usage = UsageHint::Container;
+ } else {
+ usage = UsageHint::Foreground;
+ }
}
if (usage == UsageHint::Unknown) {
if (children.size() > 1) {
@@ -431,8 +459,13 @@
drawn.join(bounds);
}
}
- mDisplayList.applyColorTransform(
- usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
+
+ if (usage == UsageHint::Container) {
+ mDisplayList.applyColorTransform(ColorTransform::Invert);
+ } else {
+ mDisplayList.applyColorTransform(usage == UsageHint::Background ? ColorTransform::Dark
+ : ColorTransform::Light);
+ }
}
void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 4d03bf1..c904542 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -129,10 +129,6 @@
StretchMask& getStretchMask() { return mStretchMask; }
- VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
-
- void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
-
bool isPropertyFieldDirty(DirtyPropertyMask field) const {
return mDirtyPropertyFields & field;
}
@@ -215,6 +211,8 @@
void output(std::ostream& output, uint32_t level);
+ void visit(std::function<void(const RenderNode&)>) const;
+
void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; }
UsageHint usageHint() const { return mUsageHint; }
@@ -222,6 +220,7 @@
int64_t uniqueId() const { return mUniqueId; }
void setIsTextureView() { mIsTextureView = true; }
+ bool isTextureView() const { return mIsTextureView; }
void markDrawStart(SkCanvas& canvas);
void markDrawEnd(SkCanvas& canvas);
@@ -234,6 +233,7 @@
void syncProperties();
void syncDisplayList(TreeObserver& observer, TreeInfo* info);
void handleForceDark(TreeInfo* info);
+ bool shouldEnableForceDark(TreeInfo* info);
void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
@@ -248,7 +248,6 @@
const int64_t mUniqueId;
String8 mName;
- sp<VirtualLightRefBase> mUserContext;
uint32_t mDirtyPropertyFields;
RenderProperties mProperties;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index ea9b6c9..008ea3a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
#include <utility>
#include "CanvasProperty.h"
+#include "FeatureFlags.h"
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
@@ -825,7 +826,9 @@
sk_sp<SkTextBlob> textBlob(builder.make());
applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
- drawTextDecorations(x, y, totalAdvance, paintCopy);
+ if (!text_feature::fix_double_underline()) {
+ drawTextDecorations(x, y, totalAdvance, paintCopy);
+ }
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 9cb50ed..4bf1790 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -175,7 +175,7 @@
const Paint& paint, const SkPath& path, size_t start,
size_t end) override;
- void onFilterPaint(Paint& paint);
+ virtual void onFilterPaint(Paint& paint);
Paint filterPaint(const Paint& src) {
Paint dst(src);
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index b58f517..c67b135 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -18,9 +18,8 @@
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
-#include "include/private/SkFixed.h"
-#include "src/core/SkTSearch.h"
+#include <cstdlib>
#include <log/log.h>
typedef int Dot14;
@@ -41,18 +40,18 @@
if (x <= 0) {
return 0;
}
- if (x >= SK_Scalar1) {
+ if (x >= 1.0f) {
return Dot14_ONE;
}
- return SkScalarToFixed(x) >> 2;
+ return static_cast<Dot14>(x * Dot14_ONE);
}
static float SkUnitCubicInterp(float value, float bx, float by, float cx, float cy) {
// pin to the unit-square, and convert to 2.14
Dot14 x = pin_and_convert(value);
- if (x == 0) return 0;
- if (x == Dot14_ONE) return SK_Scalar1;
+ if (x == 0) return 0.0f;
+ if (x == Dot14_ONE) return 1.0f;
Dot14 b = pin_and_convert(bx);
Dot14 c = pin_and_convert(cx);
@@ -84,7 +83,7 @@
A = 3 * b;
B = 3 * (c - 2 * b);
C = 3 * (b - c) + Dot14_ONE;
- return SkFixedToScalar(eval_cubic(t, A, B, C) << 2);
+ return Dot14ToFloat(eval_cubic(t, A, B, C));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -104,7 +103,7 @@
fFlags = 0;
fElemCount = static_cast<uint8_t>(elemCount);
fFrameCount = static_cast<int16_t>(frameCount);
- fRepeat = SK_Scalar1;
+ fRepeat = 1.0f;
if (fStorage) {
free(fStorage);
fStorage = nullptr;
@@ -136,17 +135,46 @@
float SkiaInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime,
const float blend[4]) {
- SkASSERT(time > prevTime && time < nextTime);
+ LOG_FATAL_IF(time < prevTime || time > nextTime);
float t = (float)(time - prevTime) / (float)(nextTime - prevTime);
return blend ? SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t;
}
+// Returns the index of where the item is or the bit not of the index
+// where the item should go in order to keep arr sorted in ascending order.
+int SkiaInterpolatorBase::binarySearch(const SkTimeCode* arr, int count, SkMSec target) {
+ if (count <= 0) {
+ return ~0;
+ }
+
+ int lo = 0;
+ int hi = count - 1;
+
+ while (lo < hi) {
+ int mid = (hi + lo) / 2;
+ SkMSec elem = arr[mid].fTime;
+ if (elem == target) {
+ return mid;
+ } else if (elem < target) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ // Check to see if target is greater or less than where we stopped
+ if (target < arr[lo].fTime) {
+ return ~lo;
+ }
+ // e.g. it should go at the end.
+ return ~(lo + 1);
+}
+
SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T, int* indexPtr,
bool* exactPtr) const {
- SkASSERT(fFrameCount > 0);
+ LOG_FATAL_IF(fFrameCount <= 0);
Result result = kNormal_Result;
- if (fRepeat != SK_Scalar1) {
+ if (fRepeat != 1.0f) {
SkMSec startTime = 0, endTime = 0; // initialize to avoid warning
this->getDuration(&startTime, &endTime);
SkMSec totalTime = endTime - startTime;
@@ -168,10 +196,8 @@
time = offsetTime + startTime;
}
- int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time, sizeof(SkTimeCode));
-
+ int index = SkiaInterpolatorBase::binarySearch(fTimes, fFrameCount, time);
bool exact = true;
-
if (index < 0) {
index = ~index;
if (index == 0) {
@@ -184,10 +210,11 @@
}
result = kFreezeEnd_Result;
} else {
+ // Need to interpolate between two frames.
exact = false;
}
}
- SkASSERT(index < fFrameCount);
+ LOG_FATAL_IF(index >= fFrameCount);
const SkTimeCode* nextTime = &fTimes[index];
SkMSec nextT = nextTime[0].fTime;
if (exact) {
@@ -207,7 +234,7 @@
}
SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
- SkASSERT(elemCount > 0);
+ LOG_FATAL_IF(elemCount <= 0);
this->reset(elemCount, frameCount);
}
@@ -221,21 +248,19 @@
fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount);
}
-#define SK_Fixed1Third (SK_Fixed1 / 3)
-#define SK_Fixed2Third (SK_Fixed1 * 2 / 3)
-
static const float gIdentityBlend[4] = {0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f};
bool SkiaInterpolator::setKeyFrame(int index, SkMSec time, const float values[],
const float blend[4]) {
- SkASSERT(values != nullptr);
+ LOG_FATAL_IF(values == nullptr);
if (blend == nullptr) {
blend = gIdentityBlend;
}
- bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode));
- SkASSERT(success);
+ // Verify the time should go after all the frames before index
+ bool success = ~index == SkiaInterpolatorBase::binarySearch(fTimes, index, time);
+ LOG_FATAL_IF(!success);
if (success) {
SkTimeCode* timeCode = &fTimes[index];
timeCode->fTime = time;
@@ -257,7 +282,7 @@
if (exact) {
memcpy(values, nextSrc, fElemCount * sizeof(float));
} else {
- SkASSERT(index > 0);
+ LOG_FATAL_IF(index <= 0);
const float* prevSrc = nextSrc - fElemCount;
diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h
index 9422cb5..62e6c1e 100644
--- a/libs/hwui/SkiaInterpolator.h
+++ b/libs/hwui/SkiaInterpolator.h
@@ -68,14 +68,16 @@
enum Flags { kMirror = 1, kReset = 2, kHasBlend = 4 };
static float ComputeRelativeT(uint32_t time, uint32_t prevTime, uint32_t nextTime,
const float blend[4] = nullptr);
- int16_t fFrameCount;
- uint8_t fElemCount;
- uint8_t fFlags;
- float fRepeat;
struct SkTimeCode {
uint32_t fTime;
float fBlend[4];
};
+ static int binarySearch(const SkTimeCode* arr, int count, uint32_t target);
+
+ int16_t fFrameCount;
+ uint8_t fElemCount;
+ uint8_t fFlags;
+ float fRepeat;
SkTimeCode* fTimes; // pointer into fStorage
void* fStorage;
};
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
new file mode 100644
index 0000000..bd0e35a
--- /dev/null
+++ b/libs/hwui/SkiaWrapper.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef SKIA_WRAPPER_H_
+#define SKIA_WRAPPER_H_
+
+#include <SkRefCnt.h>
+#include <utils/RefBase.h>
+
+namespace android::uirenderer {
+
+template <typename T>
+class SkiaWrapper : public VirtualLightRefBase {
+public:
+ sk_sp<T> getInstance() {
+ if (mInstance != nullptr && shouldDiscardInstance()) {
+ mInstance = nullptr;
+ }
+
+ if (mInstance == nullptr) {
+ mInstance = createInstance();
+ mGenerationId++;
+ }
+ return mInstance;
+ }
+
+ virtual bool shouldDiscardInstance() const { return false; }
+
+ void discardInstance() { mInstance = nullptr; }
+
+ [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
+protected:
+ virtual sk_sp<T> createInstance() = 0;
+
+private:
+ sk_sp<T> mInstance = nullptr;
+ int32_t mGenerationId = 0;
+};
+
+} // namespace android::uirenderer
+
+#endif // SKIA_WRAPPER_H_
diff --git a/libs/hwui/SkippedFrameInfo.h b/libs/hwui/SkippedFrameInfo.h
new file mode 100644
index 0000000..de56d9a
--- /dev/null
+++ b/libs/hwui/SkippedFrameInfo.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+namespace android::uirenderer {
+
+enum class SkippedFrameReason {
+ DrawingOff,
+ ContextIsStopped,
+ NothingToDraw,
+ NoOutputTarget,
+ NoBuffer,
+ AlreadyDrawn,
+};
+
+} /* namespace android::uirenderer */
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 974a5d0..ae29edf 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -20,6 +20,7 @@
#include <log/log.h>
// libshaders only exists on Android devices
#ifdef __ANDROID__
+#include <renderthread/CanvasContext.h>
#include <shaders/shaders.h>
#endif
@@ -53,8 +54,17 @@
ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+ auto colorTransform = android::mat4();
+ const auto* context = renderthread::CanvasContext::getActiveContext();
+ if (context) {
+ const auto ratio = context->targetSdrHdrRatio();
+ if (ratio > 1.0f) {
+ colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f));
+ }
+ }
+
const auto uniforms =
- shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+ shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
currentDisplayLuminanceNits, maxLuminance);
for (const auto& uniform : uniforms) {
diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp
index 750f869..717157c 100644
--- a/libs/hwui/TreeInfo.cpp
+++ b/libs/hwui/TreeInfo.cpp
@@ -24,7 +24,8 @@
: mode(mode)
, prepareTextures(mode == MODE_FULL)
, canvasContext(canvasContext)
- , disableForceDark(canvasContext.useForceDark() ? 0 : 1)
+ , disableForceDark(canvasContext.getForceDarkType() == ForceDarkType::NONE ? 1 : 0)
+ , forceDarkType(canvasContext.getForceDarkType())
, screenSize(canvasContext.getNextFrameSize()) {}
} // namespace android::uirenderer
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2bff9cb..88449f3 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,14 +16,17 @@
#pragma once
-#include "Properties.h"
-#include "utils/Macros.h"
-
#include <utils/Timers.h>
-#include "SkSize.h"
+#include <optional>
#include <string>
+#include "Properties.h"
+#include "SkSize.h"
+#include "SkippedFrameInfo.h"
+#include "utils/ForceDark.h"
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
@@ -95,6 +98,7 @@
bool updateWindowPositions = false;
int disableForceDark;
+ ForceDarkType forceDarkType = ForceDarkType::NONE;
const SkISize screenSize;
@@ -110,13 +114,13 @@
// animate itself, such as if hasFunctors is true
// This is only set if hasAnimations is true
bool requiresUiRedraw = false;
- // This is set to true if draw() can be called this frame
- // false means that we must delay until the next vsync pulse as frame
+ // This is set to nullopt if draw() can be called this frame
+ // A value means that we must delay until the next vsync pulse as frame
// production is outrunning consumption
- // NOTE that if this is false CanvasContext will set either requiresUiRedraw
+ // NOTE that if this has a value CanvasContext will set either requiresUiRedraw
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
- bool canDrawThisFrame = true;
+ std::optional<SkippedFrameReason> skippedFrameReason;
// Sentinel for animatedImageDelay meaning there is no need to post such
// a message.
static constexpr nsecs_t kNoAnimatedImageDelay = -1;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
new file mode 100644
index 0000000..ca11975
--- /dev/null
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -0,0 +1,57 @@
+package: "com.android.graphics.hwui.flags"
+
+flag {
+ name: "matrix_44"
+ namespace: "core_graphics"
+ description: "API for 4x4 matrix and related canvas functions"
+ bug: "280116960"
+}
+
+flag {
+ name: "limited_hdr"
+ namespace: "core_graphics"
+ description: "API to enable apps to restrict the amount of HDR headroom that is used"
+ bug: "234181960"
+}
+
+flag {
+ name: "hdr_10bit_plus"
+ namespace: "core_graphics"
+ description: "Use 10101010 and FP16 formats for HDR-UI when available"
+ bug: "284159488"
+}
+
+flag {
+ name: "gainmap_animations"
+ namespace: "core_graphics"
+ description: "APIs to help enable animations involving gainmaps"
+ bug: "296482289"
+}
+
+flag {
+ name: "gainmap_constructor_with_metadata"
+ namespace: "core_graphics"
+ description: "APIs to create a new gainmap with a bitmap for metadata."
+ bug: "304478551"
+}
+
+flag {
+ name: "clip_surfaceviews"
+ namespace: "core_graphics"
+ description: "Clip z-above surfaceviews to global clip rect"
+ bug: "298621623"
+}
+
+flag {
+ name: "requested_formats_v"
+ namespace: "core_graphics"
+ description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK"
+ bug: "292545615"
+}
+
+flag {
+ name: "animate_hdr_transitions"
+ namespace: "core_graphics"
+ description: "Automatically animate all changes in HDR headroom"
+ bug: "314810174"
+}
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
#include <log/log.h>
#include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp
index 905b123..19f726a 100644
--- a/libs/hwui/apex/android_canvas.cpp
+++ b/libs/hwui/apex/android_canvas.cpp
@@ -45,9 +45,9 @@
SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
- // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to
+ // If SkSurfaces::WrapPixels fails then we should as well as we will not be able to
// draw into the canvas.
- sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);
+ sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(imageInfo, buffer->bits, rowBytes);
if (surface.get() != nullptr) {
if (outBitmap) {
outBitmap->setInfo(imageInfo, rowBytes);
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
#include <sys/cdefs.h>
#include <vulkan/vulkan.h>
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 651d526..3ebf7d1 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -244,11 +244,18 @@
// This can happen if a BitmapShader is used on multiple canvas', such as a
// software + hardware canvas, which is otherwise valid as SkShader is "immutable"
std::lock_guard _lock(mUniformGuard);
- const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
- (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr));
- const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ // Compute the weight parameter that will be used to blend between the images.
+ float W = 0.f;
+ if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
+ if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
+ W = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ } else {
+ W = 1.f;
+ }
+ }
mBuilder.uniform("W") = W;
uniforms = mBuilder.uniforms();
}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 8049dc9..27773a6 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -111,7 +111,7 @@
{
std::unique_lock lock{mImageLock};
snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
- snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+ snap.mPic = mSkAnimatedImage->makePictureSnapshot();
}
return snap;
@@ -123,7 +123,7 @@
{
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
- snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+ snap.mPic = mSkAnimatedImage->makePictureSnapshot();
snap.mDurationMS = currentFrameDuration();
}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 92d875b..8344a86 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -43,12 +43,15 @@
#include <SkColor.h>
#include <SkEncodedImageFormat.h>
#include <SkHighContrastFilter.h>
-#include <SkImageEncoder.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImagePriv.h>
#include <SkJpegGainmapEncoder.h>
#include <SkPixmap.h>
#include <SkRect.h>
#include <SkStream.h>
+#include <SkJpegEncoder.h>
+#include <SkPngEncoder.h>
#include <SkWebpEncoder.h>
#include <limits>
@@ -296,7 +299,8 @@
mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
AHardwareBuffer_acquire(buffer);
setImmutable(); // HW bitmaps are always immutable
- mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace());
+ mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
+ mInfo.refColorSpace());
}
#endif
@@ -407,7 +411,12 @@
// Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
// internally and ~Bitmap won't be invoked.
// TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
+#ifdef __ANDROID__
+ // pinnable images are only supported with the Ganesh GPU backend compiled in.
+ image = SkImages::PinnableRasterFromBitmap(skiaBitmap);
+#else
image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
+#endif
}
return image;
}
@@ -528,17 +537,25 @@
return false;
}
- SkEncodedImageFormat fm;
switch (format) {
- case JavaCompressFormat::Jpeg:
- fm = SkEncodedImageFormat::kJPEG;
- break;
+ case JavaCompressFormat::Jpeg: {
+ SkJpegEncoder::Options options;
+ options.fQuality = quality;
+ return SkJpegEncoder::Encode(stream, bitmap.pixmap(), options);
+ }
case JavaCompressFormat::Png:
- fm = SkEncodedImageFormat::kPNG;
- break;
- case JavaCompressFormat::Webp:
- fm = SkEncodedImageFormat::kWEBP;
- break;
+ return SkPngEncoder::Encode(stream, bitmap.pixmap(), {});
+ case JavaCompressFormat::Webp: {
+ SkWebpEncoder::Options options;
+ if (quality >= 100) {
+ options.fCompression = SkWebpEncoder::Compression::kLossless;
+ options.fQuality = 75; // This is effort to compress
+ } else {
+ options.fCompression = SkWebpEncoder::Compression::kLossy;
+ options.fQuality = quality;
+ }
+ return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
+ }
case JavaCompressFormat::WebpLossy:
case JavaCompressFormat::WebpLossless: {
SkWebpEncoder::Options options;
@@ -548,8 +565,6 @@
return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
}
}
-
- return SkEncodeImage(stream, bitmap, fm, quality);
}
sp<uirenderer::Gainmap> Bitmap::gainmap() const {
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index cd8af3d..80b6c03 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
#include "Canvas.h"
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Properties.h"
#include "RenderNode.h"
#include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
#include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
namespace android {
@@ -34,13 +35,6 @@
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
- const Paint& paint, Canvas* canvas) {
- const SkScalar strokeWidth = fmax(thickness, 1.0f);
- const SkScalar bottom = top + strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
-}
-
void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
// paint has already been filtered by our caller, so we can ignore any filter
const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@
}
}
-static void simplifyPaint(int color, Paint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, float totalAdvance)
- : layout(layout)
- , canvas(canvas)
- , paint(paint)
- , x(x)
- , y(y)
- , totalAdvance(totalAdvance) {}
-
- void operator()(size_t start, size_t end) {
- auto glyphFunc = [&](uint16_t* text, float* positions) {
- for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
- text[textIndex++] = layout.getGlyphId(i);
- positions[posIndex++] = x + layout.getX(i);
- positions[posIndex++] = y + layout.getY(i);
- }
- };
-
- size_t glyphCount = end - start;
-
- if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
- Paint outlinePaint(paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
- outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
- // inner
- gDrawTextBlobMode = DrawTextBlobMode::HctInner;
- Paint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
- innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
- gDrawTextBlobMode = DrawTextBlobMode::Normal;
- } else {
- // standard draw path
- canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
- }
- }
-
-private:
- const minikin::Layout& layout;
- Canvas* canvas;
- const Paint& paint;
- float x;
- float y;
- float totalAdvance;
-};
-
void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
int glyphCount, const Paint& paint) {
// Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -151,7 +78,7 @@
memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount);
};
- const minikin::MinikinFont* minikinFont = font.typeface().get();
+ const minikin::MinikinFont* minikinFont = font.baseTypeface().get();
SkFont* skfont = &copied.getSkFont();
MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery());
@@ -182,6 +109,31 @@
DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
+
+ if (text_feature::fix_double_underline()) {
+ Paint copied(paint);
+ PaintFilter* filter = getPaintFilter();
+ if (filter != nullptr) {
+ filter->filterFullPaint(&copied);
+ }
+ const bool isUnderline = copied.isUnderline();
+ const bool isStrikeThru = copied.isStrikeThru();
+ if (isUnderline || isStrikeThru) {
+ const SkScalar left = x;
+ const SkScalar right = x + layout.getAdvance();
+ if (isUnderline) {
+ const SkScalar top = y + f.getUnderlinePosition();
+ drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+ }
+ if (isStrikeThru) {
+ float textSize = paint.getSkFont().getSize();
+ const float position = textSize * Paint::kStdStrikeThru_Top;
+ const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+ const SkScalar top = y + position;
+ drawStroke(left, right, top, thickness, copied, this);
+ }
+ }
+ }
}
void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d..9ec023b 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y,float totalAdvance) = 0;
+ float y, float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 0000000..2e6e976
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+ const Paint& paint, Canvas* canvas) {
+ const SkScalar strokeWidth = fmax(thickness, 1.0f);
+ const SkScalar bottom = top + strokeWidth;
+ canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+ float y, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , totalAdvance(totalAdvance)
+ , underlinePosition(0)
+ , underlineThickness(0) {}
+
+ void operator()(size_t start, size_t end) {
+ auto glyphFunc = [&](uint16_t* text, float* positions) {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = x + layout.getX(i);
+ positions[posIndex++] = y + layout.getY(i);
+ }
+ };
+
+ size_t glyphCount = end - start;
+
+ if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+ // high contrast draw path
+ int color = paint.getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+ Paint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+ // inner
+ gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+ Paint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+ gDrawTextBlobMode = DrawTextBlobMode::Normal;
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+ }
+
+ if (text_feature::fix_double_underline()) {
+ // Extract underline position and thickness.
+ if (paint.isUnderline()) {
+ SkFontMetrics metrics;
+ paint.getSkFont().getMetrics(&metrics);
+ const float textSize = paint.getSkFont().getSize();
+ SkScalar position;
+ if (!metrics.hasUnderlinePosition(&position)) {
+ position = textSize * Paint::kStdUnderline_Top;
+ }
+ SkScalar thickness;
+ if (!metrics.hasUnderlineThickness(&thickness)) {
+ thickness = textSize * Paint::kStdUnderline_Thickness;
+ }
+
+ // If multiple fonts are used, use the most bottom position and most thick stroke
+ // width as the underline position. This follows the CSS standard:
+ // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+ // <quote>
+ // The exact position and thickness of line decorations is UA-defined in this level.
+ // However, for underlines and overlines the UA must use a single thickness and
+ // position on each line for the decorations deriving from a single decorating box.
+ // </quote>
+ underlinePosition = std::max(underlinePosition, position);
+ underlineThickness = std::max(underlineThickness, thickness);
+ }
+ }
+ }
+
+ float getUnderlinePosition() const { return underlinePosition; }
+ float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+ const minikin::Layout& layout;
+ Canvas* canvas;
+ const Paint& paint;
+ float x;
+ float y;
+ float totalAdvance;
+ float underlinePosition;
+ float underlineThickness;
+};
+
+} // namespace android
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
#include <memory>
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
using namespace android;
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 34cb4ae..f4ee36ec 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,6 +30,7 @@
#include <minikin/MinikinExtent.h>
#include <minikin/MinikinPaint.h>
#include <minikin/MinikinRect.h>
+#include <utils/TypefaceUtils.h>
namespace android {
@@ -142,7 +143,7 @@
skVariation[i].value = SkFloatToScalar(variations[i].value);
}
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index e359145..7552b56d 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -16,12 +16,15 @@
#include "MinikinUtils.h"
-#include <string>
-
#include <log/log.h>
-
+#include <minikin/FamilyVariant.h>
#include <minikin/MeasuredText.h>
#include <minikin/Measurement.h>
+
+#include <optional>
+#include <string>
+
+#include "FeatureFlags.h"
#include "Paint.h"
#include "SkPathMeasure.h"
#include "Typeface.h"
@@ -43,9 +46,17 @@
minikinPaint.wordSpacing = paint->getWordSpacing();
minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
minikinPaint.localeListId = paint->getMinikinLocaleListId();
- minikinPaint.familyVariant = paint->getFamilyVariant();
minikinPaint.fontStyle = resolvedFace->fStyle;
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+
+ const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
+ if (familyVariant.has_value()) {
+ minikinPaint.familyVariant = familyVariant.value();
+ } else {
+ minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
+ ? minikin::FamilyVariant::ELEGANT
+ : minikin::FamilyVariant::DEFAULT;
+ }
return minikinPaint;
}
@@ -84,7 +95,8 @@
float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start,
- size_t count, size_t bufSize, float* advances) {
+ size_t count, size_t bufSize, float* advances,
+ minikin::MinikinRect* bounds) {
minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
const minikin::U16StringPiece textBuf(buf, bufSize);
const minikin::Range range(start, start + count);
@@ -92,7 +104,7 @@
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances);
+ endHyphen, advances, bounds);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 009b84b..61bc881 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -51,10 +51,9 @@
static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out);
- static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
- const Typeface* typeface, const uint16_t* buf,
- size_t start, size_t count, size_t bufSize,
- float* advances);
+ static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ float* advances, minikin::MinikinRect* bounds);
static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
@@ -76,7 +75,7 @@
size_t start = 0;
size_t nGlyphs = layout.nGlyphs();
for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
+ const minikin::MinikinFont* nextFont = layout.typeface(i).get();
if (i > 0 && nextFont != curFont) {
SkFont* skfont = &paint->getSkFont();
MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4a8f3e1..ef4dce5 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,18 +17,18 @@
#ifndef ANDROID_GRAPHICS_PAINT_H_
#define ANDROID_GRAPHICS_PAINT_H_
-#include "Typeface.h"
-
-#include <cutils/compiler.h>
-
#include <SkFont.h>
#include <SkPaint.h>
#include <SkSamplingOptions.h>
+#include <cutils/compiler.h>
+#include <minikin/FamilyVariant.h>
+#include <minikin/FontFamily.h>
+#include <minikin/FontFeature.h>
+#include <minikin/Hyphenator.h>
+
#include <string>
-#include <minikin/FontFamily.h>
-#include <minikin/FamilyVariant.h>
-#include <minikin/Hyphenator.h>
+#include "Typeface.h"
namespace android {
@@ -82,11 +82,15 @@
float getWordSpacing() const { return mWordSpacing; }
- void setFontFeatureSettings(const std::string& fontFeatureSettings) {
- mFontFeatureSettings = fontFeatureSettings;
+ void setFontFeatureSettings(std::string_view fontFeatures) {
+ mFontFeatureSettings = minikin::FontFeature::parse(fontFeatures);
}
- std::string getFontFeatureSettings() const { return mFontFeatureSettings; }
+ void resetFontFeatures() { mFontFeatureSettings.clear(); }
+
+ const std::vector<minikin::FontFeature>& getFontFeatureSettings() const {
+ return mFontFeatureSettings;
+ }
void setMinikinLocaleListId(uint32_t minikinLocaleListId) {
mMinikinLocaleListId = minikinLocaleListId;
@@ -94,9 +98,10 @@
uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
+ void resetFamilyVariant() { mFamilyVariant.reset(); }
void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; }
- minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; }
+ std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; }
void setStartHyphenEdit(uint32_t startHyphen) {
mHyphenEdit = minikin::packHyphenEdit(
@@ -169,9 +174,9 @@
float mLetterSpacing = 0;
float mWordSpacing = 0;
- std::string mFontFeatureSettings;
+ std::vector<minikin::FontFeature> mFontFeatureSettings;
uint32_t mMinikinLocaleListId;
- minikin::FamilyVariant mFamilyVariant;
+ std::optional<minikin::FamilyVariant> mFamilyVariant;
uint32_t mHyphenEdit = 0;
// The native Typeface object has the same lifetime of the Java Typeface
// object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 3c67edc..a9d1a2a 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -25,8 +25,9 @@
#include "MinikinSkia.h"
#include "SkPaint.h"
-#include "SkStream.h" // Fot tests.
+#include "SkStream.h" // For tests.
#include "SkTypeface.h"
+#include "utils/TypefaceUtils.h"
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
@@ -140,9 +141,8 @@
const minikin::FontStyle defaultStyle;
const minikin::MinikinFont* mf =
- families.empty()
- ? nullptr
- : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
+ families.empty() ? nullptr
+ : families[0]->getClosestMatch(defaultStyle).typeface().get();
if (mf != nullptr) {
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
const SkFontStyle& style = skTypeface->fontStyle();
@@ -187,7 +187,9 @@
LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont);
void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size));
- sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+ LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr");
+ sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index a7f5aa83..90b1da8 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-#include "GraphicsJNI.h"
-#include "ImageDecoder.h"
-#include "Utils.h"
-
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
#include <SkColorFilter.h>
@@ -27,10 +23,15 @@
#include <SkRect.h>
#include <SkRefCnt.h>
#include <hwui/AnimatedImageDrawable.h>
-#include <hwui/ImageDecoder.h>
#include <hwui/Canvas.h>
+#include <hwui/ImageDecoder.h>
#include <utils/Looper.h>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "Utils.h"
+
using namespace android;
static jclass gAnimatedImageDrawableClass;
@@ -145,8 +146,9 @@
static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jlong nativeFilter) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
- drawable->setStagingColorFilter(sk_ref_sp(filter));
+ auto filter = uirenderer::ColorFilter::fromJava(nativeFilter);
+ auto skColorFilter = filter != nullptr ? filter->getInstance() : sk_sp<SkColorFilter>();
+ drawable->setStagingColorFilter(skColorFilter);
}
static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
// #define LOG_NDEBUG 0
#include "Bitmap.h"
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
#include "BitmapFactory.h"
#include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
#include "BitmapRegionDecoder.h"
#include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 4bd7ef4..0b95148 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -15,20 +15,21 @@
** limitations under the License.
*/
-#include "GraphicsJNI.h"
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
#include "SkBlendMode.h"
-#include "SkColorFilter.h"
-#include "SkColorMatrixFilter.h"
namespace android {
using namespace uirenderer;
-class SkColorFilterGlue {
+class ColorFilterGlue {
public:
- static void SafeUnref(SkColorFilter* filter) {
- SkSafeUnref(filter);
+ static void SafeUnref(ColorFilter* filter) {
+ if (filter) {
+ filter->decStrong(nullptr);
+ }
}
static jlong GetNativeFinalizer(JNIEnv*, jobject) {
@@ -36,41 +37,75 @@
}
static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
- SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
- return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release());
+ auto mode = static_cast<SkBlendMode>(modeHandle);
+ auto* blendModeFilter = new BlendModeColorFilter(srcColor, mode);
+ blendModeFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(blendModeFilter));
}
static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
- return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release());
+ auto* lightingFilter = new LightingFilter(mul, add);
+ lightingFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(lightingFilter));
}
- static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
- float matrix[20];
- env->GetFloatArrayRegion(jarray, 0, 20, matrix);
+ static void SetLightingFilterMul(JNIEnv* env, jobject, jlong lightingFilterPtr, jint mul) {
+ auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+ if (filter) {
+ filter->setMul(mul);
+ }
+ }
+
+ static void SetLightingFilterAdd(JNIEnv* env, jobject, jlong lightingFilterPtr, jint add) {
+ auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+ if (filter) {
+ filter->setAdd(add);
+ }
+ }
+
+ static std::vector<float> getMatrixFromJFloatArray(JNIEnv* env, jfloatArray jarray) {
+ std::vector<float> matrix(20);
+ // float matrix[20];
+ env->GetFloatArrayRegion(jarray, 0, 20, matrix.data());
// java biases the translates by 255, so undo that before calling skia
matrix[ 4] *= (1.0f/255);
matrix[ 9] *= (1.0f/255);
matrix[14] *= (1.0f/255);
matrix[19] *= (1.0f/255);
- return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release());
+ return matrix;
+ }
+
+ static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+ std::vector<float> matrix = getMatrixFromJFloatArray(env, jarray);
+ auto* colorMatrixColorFilter = new ColorMatrixColorFilter(std::move(matrix));
+ colorMatrixColorFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(colorMatrixColorFilter));
+ }
+
+ static void SetColorMatrix(JNIEnv* env, jobject, jlong colorMatrixColorFilterPtr,
+ jfloatArray jarray) {
+ auto* filter = reinterpret_cast<ColorMatrixColorFilter*>(colorMatrixColorFilterPtr);
+ if (filter) {
+ filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
+ }
}
};
static const JNINativeMethod colorfilter_methods[] = {
- {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer }
-};
+ {"nativeGetFinalizer", "()J", (void*)ColorFilterGlue::GetNativeFinalizer}};
static const JNINativeMethod blendmode_methods[] = {
- { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter },
+ {"native_CreateBlendModeFilter", "(II)J", (void*)ColorFilterGlue::CreateBlendModeFilter},
};
static const JNINativeMethod lighting_methods[] = {
- { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter },
-};
+ {"native_CreateLightingFilter", "(II)J", (void*)ColorFilterGlue::CreateLightingFilter},
+ {"native_SetLightingFilterAdd", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterAdd},
+ {"native_SetLightingFilterMul", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterMul}};
static const JNINativeMethod colormatrix_methods[] = {
- { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter },
-};
+ {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
+ {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
int register_android_graphics_ColorFilter(JNIEnv* env) {
android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
index 15e529e..a66d3b8 100644
--- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
+++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
@@ -1,11 +1,11 @@
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkData.h"
-#include "SkMalloc.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
#include "Utils.h"
+#include <cstdlib>
#include <nativehelper/JNIHelp.h>
#include <log/log.h>
#include <memory>
@@ -177,6 +177,10 @@
return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
}
+static void free_pointer_skproc(const void* ptr, void*) {
+ free((void*)ptr);
+}
+
sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) {
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage));
if (!stream) {
@@ -186,19 +190,31 @@
size_t bufferSize = 4096;
size_t streamLen = 0;
size_t len;
- char* data = (char*)sk_malloc_throw(bufferSize);
+ char* data = (char*)malloc(bufferSize);
+ LOG_ALWAYS_FATAL_IF(!data);
while ((len = stream->read(data + streamLen,
bufferSize - streamLen)) != 0) {
streamLen += len;
if (streamLen == bufferSize) {
bufferSize *= 2;
- data = (char*)sk_realloc_throw(data, bufferSize);
+ data = (char*)realloc(data, bufferSize);
+ LOG_ALWAYS_FATAL_IF(!data);
}
}
- data = (char*)sk_realloc_throw(data, streamLen);
-
- return SkData::MakeFromMalloc(data, streamLen);
+ if (streamLen == 0) {
+ // realloc with size 0 is unspecified behavior in C++11
+ free(data);
+ data = nullptr;
+ } else {
+ // Trim down the buffer to the actual size of the data.
+ LOG_FATAL_IF(streamLen > bufferSize);
+ data = (char*)realloc(data, streamLen);
+ LOG_ALWAYS_FATAL_IF(!data);
+ }
+ // Just in case sk_free differs from free, we ask Skia to use
+ // free to cleanup the buffer that SkData wraps.
+ return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr);
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 69774cc..e6d790f 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include "FontUtils.h"
@@ -34,6 +31,7 @@
#include <minikin/FontFamily.h>
#include <minikin/LocaleList.h>
#include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
#include <memory>
@@ -89,7 +87,8 @@
}
std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
builder->langId, builder->variant, std::move(builder->fonts),
- true /* isCustomFallback */, false /* isDefaultFallback */);
+ true /* isCustomFallback */, false /* isDefaultFallback */,
+ minikin::VariationFamilyType::None);
if (family->getCoverage().length() == 0) {
return 0;
}
@@ -127,7 +126,7 @@
args.setCollectionIndex(ttcIndex);
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
if (face == NULL) {
ALOGE("addFont failed to create font, invalid request");
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7..0fffee7 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@
p.writeFloat(info.fDisplayRatioHdr);
// base image type
p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
- // type
- p.writeInt32(static_cast<int32_t>(info.fType));
#else
doThrowRE(env, "Cannot use parcels outside of Android!");
#endif
@@ -232,7 +230,6 @@
info.fDisplayRatioSdr = p.readFloat();
info.fDisplayRatioHdr = p.readFloat();
info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
- info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
fromJava(nativeObject)->info = info;
#else
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
#include <assert.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 23ab5dd..b9fff36 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -125,14 +125,6 @@
static jobject createBitmapRegionDecoder(JNIEnv* env,
android::BitmapRegionDecoderWrapper* bitmap);
- /**
- * Given a bitmap we natively allocate a memory block to store the contents
- * of that bitmap. The memory is then attached to the bitmap via an
- * SkPixelRef, which ensures that upon deletion the appropriate caches
- * are notified.
- */
- static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap);
-
/** Copy the colors in colors[] to the bitmap, convert to the correct
format along the way.
Whether to use premultiplied pixels is determined by dstBitmap's alphaType.
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
#include <JankTracker.h>
#include <log/log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 048ce02..cbd4520 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -1,6 +1,5 @@
#include "GraphicsJNI.h"
#include "SkMaskFilter.h"
-#include "SkBlurMask.h"
#include "SkBlurMaskFilter.h"
#include "SkBlurTypes.h"
#include "SkTableMaskFilter.h"
@@ -11,6 +10,13 @@
}
}
+// From https://skia.googlesource.com/skia/+/d74c99a3cd5eef5f16b2eb226e6b45fe523c8552/src/core/SkBlurMask.cpp#28
+static constexpr float kBLUR_SIGMA_SCALE = 0.57735f;
+
+static float convertRadiusToSigma(float radius) {
+ return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
class SkMaskFilterGlue {
public:
static void destructor(JNIEnv* env, jobject, jlong filterHandle) {
@@ -19,7 +25,7 @@
}
static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
- SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+ SkScalar sigma = convertRadiusToSigma(radius);
SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release();
ThrowIAE_IfNull(env, filter);
return reinterpret_cast<jlong>(filter);
@@ -34,7 +40,7 @@
direction[i] = values[i];
}
- SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+ SkScalar sigma = convertRadiusToSigma(radius);
SkMaskFilter* filter = SkBlurMaskFilter::MakeEmboss(sigma,
direction, ambient, specular).release();
ThrowIAE_IfNull(env, filter);
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "9patch"
#define LOG_NDEBUG 1
#include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 13357fa..d84b73d 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,16 +15,30 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
-#include <utils/Log.h>
-
-#include "GraphicsJNI.h"
+#include <hwui/BlurDrawLooper.h>
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/GraphemeBreak.h>
+#include <minikin/LocaleList.h>
+#include <minikin/Measurement.h>
+#include <minikin/MinikinPaint.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
+#include <unicode/utf16.h>
+#include <utils/Log.h>
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
#include "SkColorFilter.h"
#include "SkColorSpace.h"
#include "SkFont.h"
@@ -35,28 +49,22 @@
#include "SkPathEffect.h"
#include "SkPathUtils.h"
#include "SkShader.h"
-#include "SkBlendMode.h"
#include "unicode/uloc.h"
#include "utils/Blur.h"
-#include <hwui/BlurDrawLooper.h>
-#include <hwui/MinikinSkia.h>
-#include <hwui/MinikinUtils.h>
-#include <hwui/Paint.h>
-#include <hwui/Typeface.h>
-#include <minikin/GraphemeBreak.h>
-#include <minikin/LocaleList.h>
-#include <minikin/Measurement.h>
-#include <minikin/MinikinPaint.h>
-#include <unicode/utf16.h>
-
-#include <cassert>
-#include <cstring>
-#include <memory>
-#include <vector>
-
namespace android {
+namespace {
+
+void copyMinikinRectToSkRect(const minikin::MinikinRect& minikinRect, SkRect* skRect) {
+ skRect->fLeft = minikinRect.mLeft;
+ skRect->fTop = minikinRect.mTop;
+ skRect->fRight = minikinRect.mRight;
+ skRect->fBottom = minikinRect.mBottom;
+}
+
+} // namespace
+
static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
const SkPoint pos[], SkPath* dst) {
dst->reset();
@@ -105,8 +113,8 @@
float measured = 0;
std::unique_ptr<float[]> advancesArray(new float[count]);
- MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text,
- 0, count, count, advancesArray.get());
+ MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
+ count, count, advancesArray.get(), nullptr);
for (int i = 0; i < count; i++) {
// traverse in the given direction
@@ -196,9 +204,9 @@
if (advances) {
advancesArray.reset(new jfloat[count]);
}
- const float advance = MinikinUtils::measureText(paint,
- static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount,
- advancesArray.get());
+ const float advance = MinikinUtils::measureText(
+ paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
+ contextCount, advancesArray.get(), nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
@@ -236,7 +244,7 @@
minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
- advancesArray.get());
+ advancesArray.get(), nullptr);
size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
start, count, offset, moveOpt);
return static_cast<jint>(result);
@@ -500,7 +508,7 @@
static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
const jchar buf[], jint start, jint count, jint bufSize,
jboolean isRtl, jint offset, jfloatArray advances,
- jint advancesIndex) {
+ jint advancesIndex, SkRect* drawBounds) {
if (advances) {
size_t advancesLength = env->GetArrayLength(advances);
if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -509,14 +517,23 @@
}
}
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+ minikin::MinikinRect bounds;
if (offset == start + count && advances == nullptr) {
- return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
- bufSize, nullptr);
+ float result =
+ MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+ bufSize, nullptr, drawBounds ? &bounds : nullptr);
+ if (drawBounds) {
+ copyMinikinRectToSkRect(bounds, drawBounds);
+ }
+ return result;
}
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get());
+ advancesArray.get(), drawBounds ? &bounds : nullptr);
+ if (drawBounds) {
+ copyMinikinRectToSkRect(bounds, drawBounds);
+ }
float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
if (advances) {
minikin::distributeAdvances(advancesArray.get(), buf, start, count);
@@ -532,7 +549,7 @@
ScopedCharArrayRO textArray(env, text);
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, nullptr, 0);
+ isRtl, offset - contextStart, nullptr, 0, nullptr);
return result;
}
@@ -540,13 +557,19 @@
jcharArray text, jint start, jint end,
jint contextStart, jint contextEnd,
jboolean isRtl, jint offset,
- jfloatArray advances, jint advancesIndex) {
+ jfloatArray advances, jint advancesIndex,
+ jobject drawBounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
+ SkRect skDrawBounds;
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, advances, advancesIndex);
+ isRtl, offset - contextStart, advances, advancesIndex,
+ drawBounds ? &skDrawBounds : nullptr);
+ if (drawBounds != nullptr) {
+ GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
+ }
return result;
}
@@ -555,7 +578,7 @@
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get());
+ advancesArray.get(), nullptr);
return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
}
@@ -571,7 +594,7 @@
return result;
}
- static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
+ static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
const int kElegantAscent = 1900;
@@ -584,7 +607,7 @@
minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = font->getSkewX();
bool savefakeBold = font->isEmbolden();
- MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+ MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
SkScalar spacing = font->getMetrics(metrics);
// The populateSkPaint call may have changed fake bold / text skew
// because we want to measure with those effects applied, so now
@@ -600,6 +623,17 @@
metrics->fLeading = size * kElegantLeading / 2048;
spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
}
+
+ if (useLocale) {
+ minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+ minikin::MinikinExtent extent =
+ typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+ metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
+ metrics->fDescent = std::max(extent.descent, metrics->fDescent);
+ metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
+ metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom);
+ }
+
return spacing;
}
@@ -612,7 +646,7 @@
MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
metrics.fAscent = extent.ascent;
metrics.fDescent = extent.descent;
@@ -657,27 +691,29 @@
jstring settings) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
if (!settings) {
- paint->setFontFeatureSettings(std::string());
+ paint->resetFontFeatures();
} else {
ScopedUtfChars settingsChars(env, settings);
- paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+ paint->setFontFeatureSettings(
+ std::string_view(settingsChars.c_str(), settingsChars.size()));
}
}
- static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+ static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+ jboolean useLocale) {
SkFontMetrics metrics;
- SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
+ SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale);
GraphicsJNI::set_metrics(env, metricsObj, metrics);
return SkScalarToFloat(spacing);
}
- static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+ static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+ jboolean useLocale) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, useLocale);
return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
}
-
// ------------------ @CriticalNative ---------------------------
static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
@@ -821,9 +857,11 @@
static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
Paint* obj = reinterpret_cast<Paint *>(objHandle);
- SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle);
- obj->setColorFilter(sk_ref_sp(filter));
- return reinterpret_cast<jlong>(obj->getColorFilter());
+ auto colorFilter = uirenderer::ColorFilter::fromJava(filterHandle);
+ auto skColorFilter =
+ colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
+ obj->setColorFilter(skColorFilter);
+ return filterHandle;
}
static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) {
@@ -899,15 +937,39 @@
obj->setMinikinLocaleListId(minikinLocaleListId);
}
- static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
+ // Note: Following three values must be equal to the ones in Java file: Paint.java.
+ constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1;
+ constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+ constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
+ static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT;
+ const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant();
+ if (familyVariant.has_value()) {
+ if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) {
+ return ELEGANT_TEXT_HEIGHT_ENABLED;
+ } else {
+ return ELEGANT_TEXT_HEIGHT_DISABLED;
+ }
+ } else {
+ return ELEGANT_TEXT_HEIGHT_UNSET;
+ }
}
- static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) {
+ static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) {
Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- obj->setFamilyVariant(
- aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT);
+ switch (value) {
+ case ELEGANT_TEXT_HEIGHT_ENABLED:
+ obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT);
+ return;
+ case ELEGANT_TEXT_HEIGHT_DISABLED:
+ obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT);
+ return;
+ case ELEGANT_TEXT_HEIGHT_UNSET:
+ default:
+ obj->resetFamilyVariant();
+ return;
+ }
}
static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
@@ -978,19 +1040,19 @@
static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
return SkScalarToFloat(metrics.fAscent);
}
static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
return SkScalarToFloat(metrics.fDescent);
}
static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
SkScalar position;
if (metrics.hasUnderlinePosition(&position)) {
return SkScalarToFloat(position);
@@ -1002,7 +1064,7 @@
static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
SkScalar thickness;
if (metrics.hasUnderlineThickness(&thickness)) {
return SkScalarToFloat(thickness);
@@ -1083,7 +1145,7 @@
(void*)PaintGlue::getCharArrayBounds},
{"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
- {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F",
+ {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
(void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
{"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
@@ -1097,9 +1159,9 @@
{"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
{"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
(void*)PaintGlue::setFontFeatureSettings},
- {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+ {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F",
(void*)PaintGlue::getFontMetrics},
- {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+ {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I",
(void*)PaintGlue::getFontMetricsInt},
// --------------- @CriticalNative ------------------
@@ -1142,8 +1204,8 @@
{"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
{"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
(void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
- {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
- {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+ {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight},
+ {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight},
{"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
{"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
{"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index f3db170..dcd3fa4 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
#include "Bitmap.h"
+#include "ColorFilter.h"
#include "GraphicsJNI.h"
#include "SkBlendMode.h"
#include "SkImageFilter.h"
#include "SkImageFilters.h"
#include "graphics_jni_helpers.h"
#include "utils/Blur.h"
-#include <utils/Log.h>
using namespace android::uirenderer;
@@ -76,11 +76,13 @@
jlong colorFilterHandle,
jlong inputFilterHandle
) {
- auto* colorFilter = reinterpret_cast<const SkColorFilter*>(colorFilterHandle);
+ auto colorFilter = android::uirenderer::ColorFilter::fromJava(colorFilterHandle);
+ auto skColorFilter =
+ colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle);
- sk_sp<SkImageFilter> colorFilterImageFilter = SkImageFilters::ColorFilter(
- sk_ref_sp(colorFilter), sk_ref_sp(inputFilter), nullptr);
- return reinterpret_cast<jlong>(colorFilterImageFilter.release());
+ sk_sp<SkImageFilter> colorFilterImageFilter =
+ SkImageFilters::ColorFilter(skColorFilter, sk_ref_sp(inputFilter), nullptr);
+ return reinterpret_cast<jlong>(colorFilterImageFilter.release());
}
static jlong createBlendModeEffect(
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..a952be0 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
#include <vector>
#include "Gainmap.h"
@@ -68,21 +65,41 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
}
-static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool isDirectSampled,
- const SkSamplingOptions& sampling) {
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkGainmapInfo sNoOpGainmap = {
+ .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0},
+ .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0},
+ .fGainmapGamma = {1.f, 1.f, 1.f, 1.f},
+ .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0},
+ .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0},
+ .fDisplayRatioSdr = 1.f,
+ .fDisplayRatioHdr = 1.f,
+};
+
+static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+ jint tileModeX, jint tileModeY, jint maxAniso, bool filter,
+ bool isDirectSampled, jlong overrideGainmapPtr) {
+ SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso))
+ : SkSamplingOptions(filter ? SkFilterMode::kLinear
+ : SkFilterMode::kNearest,
+ SkMipmapMode::kNone);
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr);
sk_sp<SkImage> image;
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
image = bitmap.makeImage();
+ if (!gainmap && bitmap.hasGainmap()) {
+ gainmap = bitmap.gainmap().get();
+ }
- if (!isDirectSampled && bitmap.hasGainmap()) {
- sk_sp<SkShader> gainmapShader = MakeGainmapShader(
- image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
- (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) {
+ sk_sp<SkShader> gainmapShader =
+ MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
if (gainmapShader) {
if (matrix) {
gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
@@ -114,26 +131,6 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool filter,
- bool isDirectSampled) {
- SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
- SkMipmapMode::kNone);
- return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
- isDirectSampled, sampling);
-}
-
-static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr,
- jlong bitmapHandle, jint tileModeX,
- jint tileModeY, jint maxAniso,
- bool isDirectSampled) {
- auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso));
- return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
- isDirectSampled, sampling);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
const size_t count = env->GetArrayLength(colorArray);
const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
@@ -422,8 +419,7 @@
};
static const JNINativeMethod gBitmapShaderMethods[] = {
- {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
- {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso},
+ {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor},
};
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 7298906..3533001 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkStream.h"
#include "YuvToJpegEncoder.h"
@@ -336,7 +333,8 @@
bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
- int width, int height, int jpegQuality) {
+ int width, int height, int jpegQuality, ScopedByteArrayRO* jExif,
+ ScopedIntArrayRO* jHdrStrides, ScopedIntArrayRO* jSdrStrides) {
// Check SDR color space. Now we only support SRGB transfer function
if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) {
jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
@@ -344,6 +342,19 @@
"The requested SDR color space is not supported. Transfer function must be SRGB");
return false;
}
+ // Check HDR and SDR strides length.
+ // HDR is YCBCR_P010 color format, and its strides length must be 2 (Y, chroma (Cb, Cr)).
+ // SDR is YUV_420_888 color format, and its strides length must be 3 (Y, Cb, Cr).
+ if (jHdrStrides->size() != 2) {
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException, "HDR stride length must be 2.");
+ return false;
+ }
+ if (jSdrStrides->size() != 3) {
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException, "SDR stride length must be 3.");
+ return false;
+ }
ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
@@ -355,20 +366,32 @@
return false;
}
+ const int* hdrStrides = reinterpret_cast<const int*>(jHdrStrides->get());
+ const int* sdrStrides = reinterpret_cast<const int*>(jSdrStrides->get());
+
JpegR jpegREncoder;
jpegr_uncompressed_struct p010;
p010.data = hdr;
p010.width = width;
p010.height = height;
+ // Divided by 2 because unit in libultrader is pixel and in YuvImage it is byte.
+ p010.luma_stride = (hdrStrides[0] + 1) / 2;
+ p010.chroma_stride = (hdrStrides[1] + 1) / 2;
p010.colorGamut = hdrColorGamut;
jpegr_uncompressed_struct yuv420;
yuv420.data = sdr;
yuv420.width = width;
yuv420.height = height;
+ yuv420.luma_stride = sdrStrides[0];
+ yuv420.chroma_stride = sdrStrides[1];
yuv420.colorGamut = sdrColorGamut;
+ jpegr_exif_struct exif;
+ exif.data = const_cast<void*>(reinterpret_cast<const void*>(jExif->get()));
+ exif.length = jExif->size();
+
jpegr_compressed_struct jpegR;
jpegR.maxLength = width * height * sizeof(uint8_t);
@@ -377,7 +400,8 @@
if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
hdrTransferFunction,
- &jpegR, jpegQuality, nullptr); success != android::OK) {
+ &jpegR, jpegQuality,
+ exif.length > 0 ? &exif : NULL); success != JPEGR_NO_ERROR) {
ALOGW("Encode JPEG/R failed, error code: %d.", success);
return false;
}
@@ -419,20 +443,27 @@
static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
jint width, jint height, jint quality, jobject jstream,
- jbyteArray jstorage) {
+ jbyteArray jstorage, jbyteArray jExif,
+ jintArray jHdrStrides, jintArray jSdrStrides) {
jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+ ScopedByteArrayRO exif(env, jExif);
+ ScopedIntArrayRO hdrStrides(env, jHdrStrides);
+ ScopedIntArrayRO sdrStrides(env, jSdrStrides);
+
SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
P010Yuv420ToJpegREncoder encoder;
jboolean result = JNI_FALSE;
if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
- width, height, quality)) {
+ width, height, quality, &exif,
+ &hdrStrides, &sdrStrides)) {
result = JNI_TRUE;
}
env->ReleaseByteArrayElements(inHdr, hdr, 0);
env->ReleaseByteArrayElements(inSdr, sdr, 0);
+
delete strm;
return result;
}
@@ -441,7 +472,7 @@
static const JNINativeMethod gYuvImageMethods[] = {
{ "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
(void*)YuvImage_compressToJpeg },
- { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z",
+ { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B[B[I[I)Z",
(void*)YuvImage_compressToJpegR }
};
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 0e711ef..629f1e6 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,6 +2,7 @@
#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
#include <android/data_space.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <ultrahdr/jpegr.h>
extern "C" {
@@ -90,11 +91,15 @@
* @param width Width of the Yuv data in terms of pixels.
* @param height Height of the Yuv data in terms of pixels.
* @param jpegQuality Picture quality in [0, 100].
+ * @param exif Buffer holds EXIF package.
+ * @param hdrStrides The number of row bytes in each image plane of the HDR input.
+ * @param sdrStrides The number of row bytes in each image plane of the SDR input.
* @return true if successfully compressed the stream.
*/
bool encode(JNIEnv* env,
SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
- int width, int height, int jpegQuality);
+ int width, int height, int jpegQuality, ScopedByteArrayRO* exif,
+ ScopedIntArrayRO* hdrStrides, ScopedIntArrayRO* sdrStrides);
/** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
* used in JPEG/R
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index f060bb3..426644e 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -84,7 +84,7 @@
canvas->resetRecording(width, height, renderNode);
}
-static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) {
+static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
#else
@@ -175,14 +175,14 @@
const char* const kClassPathName = "android/graphics/RecordingCanvas";
static JNINativeMethod gMethods[] = {
+ {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+ {"nGetMaximumTextureHeight", "()I",
+ (void*)android_view_DisplayListCanvas_getMaxTextureSize},
// ------------ @CriticalNative --------------
{"nCreateDisplayListCanvas", "(JII)J",
(void*)android_view_DisplayListCanvas_createDisplayListCanvas},
{"nResetDisplayListCanvas", "(JJII)V",
(void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
- {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
- {"nGetMaximumTextureHeight", "()I",
- (void*)android_view_DisplayListCanvas_getMaxTextureSize},
{"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
{"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
{"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d04de37..d15b1680 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <FrameInfo.h>
@@ -27,7 +25,7 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
@@ -35,7 +33,9 @@
#include <SkTypeface.h>
#include <dlfcn.h>
#include <gui/TraceUtils.h>
+#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
+#include <log/log.h>
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
@@ -58,6 +58,7 @@
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
+#include "utils/ForceDark.h"
namespace android {
@@ -477,7 +478,7 @@
// actually cross thread boundaries here, make a copy so it's immutable proper
if (bitmap && !bitmap->isImmutable()) {
ATRACE_NAME("Copying mutable bitmap");
- return SkImage::MakeFromBitmap(*bitmap);
+ return SkImages::RasterFromBitmap(*bitmap);
}
if (img->isTextureBacked()) {
ATRACE_NAME("Readback of texture image");
@@ -497,7 +498,7 @@
return sk_ref_sp(img);
}
bm.setImmutable();
- return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+ return SkImages::PinnableRasterFromBitmap(bm);
}
return sk_ref_sp(img);
}
@@ -524,7 +525,16 @@
if (iter != context->mTextureMap.end()) {
img = iter->second.get();
}
- return img->encodeToData();
+ if (!img) {
+ return nullptr;
+ }
+ // The following encode (specifically the pixel readback) will fail on a
+ // texture-backed image. They should already be raster images, but on
+ // the off-chance they aren't, we will just serialize it as nothing.
+ if (img->isTextureBacked()) {
+ return SkData::MakeEmpty();
+ }
+ return SkPngEncoder::Encode(nullptr, img, {});
}
void serialize(SkWStream* stream) const override {
@@ -815,10 +825,10 @@
proxy->allocateBuffers();
}
-static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jboolean enable) {
+static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz, jlong proxyPtr,
+ jint type) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setForceDark(enable);
+ proxy->setForceDark(static_cast<ForceDarkType>(type));
}
static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
@@ -1007,7 +1017,7 @@
{"nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess},
{"nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority},
{"nAllocateBuffers", "(J)V", (void*)android_view_ThreadedRenderer_allocateBuffers},
- {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
+ {"nSetForceDark", "(JI)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
{"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 2a218a2..a7d6423 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -568,6 +568,7 @@
struct {
jclass clazz;
jmethodID callPositionChanged;
+ jmethodID callPositionChanged2;
jmethodID callApplyStretch;
jmethodID callPositionLost;
} gPositionListener;
@@ -589,14 +590,31 @@
virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
if (CC_UNLIKELY(!mListener || !info.updateWindowPositions)) return;
- Matrix4 transform;
- info.damageAccumulator->computeCurrentTransform(&transform);
const RenderProperties& props = node.properties();
+ const bool enableClip = Properties::clipSurfaceViews;
- uirenderer::Rect bounds(props.getWidth(), props.getHeight());
+ Matrix4 transform;
+ SkIRect clipBounds;
+ if (enableClip) {
+ uirenderer::Rect initialClipBounds;
+ const auto clipFlags = props.getClippingFlags();
+ if (clipFlags) {
+ props.getClippingRectForFlags(clipFlags, &initialClipBounds);
+ } else {
+ // Works for RenderNode::damageSelf()
+ initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+ }
+ clipBounds =
+ info.damageAccumulator
+ ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
+ .roundOut();
+ } else {
+ info.damageAccumulator->computeCurrentTransform(&transform);
+ }
bool useStretchShader =
Properties::getStretchEffectBehavior() != StretchEffectBehavior::UniformScale;
// Compute the transform bounds first before calculating the stretch
+ uirenderer::Rect bounds(props.getWidth(), props.getHeight());
transform.mapRect(bounds);
bool hasStretch = useStretchShader && info.stretchEffectCount;
@@ -614,10 +632,11 @@
bounds.roundOut();
}
- if (mPreviousPosition == bounds) {
+ if (mPreviousPosition == bounds && mPreviousClip == clipBounds) {
return;
}
mPreviousPosition = bounds;
+ mPreviousClip = clipBounds;
ATRACE_NAME("Update SurfaceView position");
@@ -629,11 +648,23 @@
// In particular if the app removes a view from the view tree before
// this callback is dispatched, then we lose the position
// information for this frame.
- jboolean keepListening = env->CallStaticBooleanMethod(
- gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
- static_cast<jlong>(info.canvasContext.getFrameNumber()),
- static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
- static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+ jboolean keepListening;
+ if (!enableClip) {
+ keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
+ static_cast<jlong>(info.canvasContext.getFrameNumber()),
+ static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+ static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+ } else {
+ keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callPositionChanged2, mListener,
+ static_cast<jlong>(info.canvasContext.getFrameNumber()),
+ static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+ static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
+ static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
+ static_cast<jint>(clipBounds.fRight),
+ static_cast<jint>(clipBounds.fBottom));
+ }
if (!keepListening) {
env->DeleteGlobalRef(mListener);
mListener = nullptr;
@@ -738,6 +769,7 @@
JavaVM* mVm;
jobject mListener;
uirenderer::Rect mPreviousPosition;
+ uirenderer::Rect mPreviousClip;
};
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -866,6 +898,8 @@
gPositionListener.clazz = MakeGlobalRefOrDie(env, clazz);
gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
+ gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
+ env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Interpolator.h>
#include <cutils/log.h>
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Animator.h>
#include <Interpolator.h>
#include <RenderProperties.h>
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb..ade48f2 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#include "GraphicsJNI.h"
+#include <hwui/Paint.h>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
#include "PathParser.h"
#include "VectorDrawable.h"
-#include <hwui/Paint.h>
-
namespace android {
using namespace uirenderer;
using namespace uirenderer::VectorDrawable;
@@ -108,8 +108,9 @@
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
SkRect rect;
GraphicsJNI::jrect_to_rect(env, jrect, &rect);
- SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr);
- return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache);
+ auto colorFilter = ColorFilter::fromJava(colorFilterPtr);
+ auto skColorFilter = colorFilter != nullptr ? colorFilter->getInstance() : nullptr;
+ return tree->draw(canvas, skColorFilter.get(), rect, needsMirroring, canReuseCache);
}
/**
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 1af60b2..f405aba 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "Font.h"
#include "SkData.h"
#include "SkFont.h"
@@ -40,6 +37,7 @@
#include <minikin/LocaleList.h>
#include <minikin/SystemFonts.h>
#include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
#include <memory>
@@ -127,7 +125,7 @@
static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
jint weight, jboolean italic, jint ttcIndex) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
// Reconstruct SkTypeface with different arguments from existing SkTypeface.
@@ -159,7 +157,7 @@
static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
jlong paintHandle, jobject rect) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkFont* skFont = &paint->getSkFont();
@@ -179,7 +177,7 @@
static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
jobject metricsObj) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkFont* skFont = &paint->getSkFont();
@@ -209,7 +207,7 @@
// Fast Native
static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()),
minikinFont->GetFontSize());
}
@@ -217,7 +215,7 @@
// Critical Native
static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- return reinterpret_cast<jlong>(font->font->typeface()->GetFontData());
+ return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData());
}
// Critical Native
@@ -236,7 +234,7 @@
}
return env->NewStringUTF(path.c_str());
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
const std::string& path = minikinFont->GetFontPath();
if (path.empty()) {
return nullptr;
@@ -275,7 +273,7 @@
reader.skipString(); // fontPath
return reader.read<int>();
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return minikinFont->GetFontIndex();
}
}
@@ -289,7 +287,7 @@
reader.skip<int>(); // fontIndex
return reader.readArray<minikin::FontVariation>().second;
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return minikinFont->GetAxes().size();
}
}
@@ -304,7 +302,7 @@
reader.skip<int>(); // fontIndex
var = reader.readArray<minikin::FontVariation>().first[index];
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
var = minikinFont->GetAxes().at(index);
}
uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
@@ -314,7 +312,7 @@
// Critical Native
static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- return font->font->typeface()->GetSourceId();
+ return font->font->baseTypeface()->GetSourceId();
}
static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) {
@@ -462,7 +460,7 @@
args.setCollectionIndex(ttcIndex);
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
if (face == nullptr) {
return nullptr;
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index ee158ee..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedUtfChars.h>
@@ -60,7 +57,7 @@
// Regular JNI
static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
jstring langTags, jint variant, jboolean isCustomFallback,
- jboolean isDefaultFallback) {
+ jboolean isDefaultFallback, jint variationFamilyType) {
std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
uint32_t localeId;
if (langTags == nullptr) {
@@ -71,7 +68,8 @@
}
std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
- isCustomFallback, isDefaultFallback);
+ isCustomFallback, isDefaultFallback,
+ static_cast<minikin::VariationFamilyType>(variationFamilyType));
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -121,7 +119,7 @@
static const JNINativeMethod gFontFamilyBuilderMethods[] = {
{"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
{"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
- {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+ {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build},
{"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
};
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
#include <sys/types.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
#include "PdfUtils.h"
-#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
#include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
namespace android {
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
#include <minikin/GraphemeBreak.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..c512256 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
#include "utils/misc.h"
#include "utils/Log.h"
#include "graphics_jni_helpers.h"
@@ -54,13 +51,12 @@
// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
// hyphenFrequency)
-static jlong nInit(JNIEnv* env, jclass /* unused */,
- jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, jintArray indents) {
+static jlong nInit(JNIEnv* env, jclass /* unused */, jint breakStrategy, jint hyphenationFrequency,
+ jboolean isJustified, jintArray indents, jboolean useBoundsForWidth) {
return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative(
static_cast<minikin::BreakStrategy>(breakStrategy),
- static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
- isJustified,
- jintArrayToFloatVector(env, indents)));
+ static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), isJustified,
+ jintArrayToFloatVector(env, indents), useBoundsForWidth));
}
static void nFinish(jlong nativePtr) {
@@ -131,39 +127,44 @@
}
static const JNINativeMethod gMethods[] = {
- // Fast Natives
- {"nInit", "("
- "I" // breakStrategy
- "I" // hyphenationFrequency
- "Z" // isJustified
- "[I" // indents
- ")J", (void*) nInit},
+ // Fast Natives
+ {"nInit",
+ "("
+ "I" // breakStrategy
+ "I" // hyphenationFrequency
+ "Z" // isJustified
+ "[I" // indents
+ "Z" // useBoundsForWidth
+ ")J",
+ (void*)nInit},
- // Critical Natives
- {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},
+ // Critical Natives
+ {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc},
- // Regular JNI
- {"nComputeLineBreaks", "("
- "J" // nativePtr
- "[C" // text
- "J" // MeasuredParagraph ptr.
- "I" // length
- "F" // firstWidth
- "I" // firstWidthLineCount
- "F" // restWidth
- "[F" // variableTabStops
- "F" // defaultTabStop
- "I" // indentsOffset
- ")J", (void*) nComputeLineBreaks},
+ // Regular JNI
+ {"nComputeLineBreaks",
+ "("
+ "J" // nativePtr
+ "[C" // text
+ "J" // MeasuredParagraph ptr.
+ "I" // length
+ "F" // firstWidth
+ "I" // firstWidthLineCount
+ "F" // restWidth
+ "[F" // variableTabStops
+ "F" // defaultTabStop
+ "I" // indentsOffset
+ ")J",
+ (void*)nComputeLineBreaks},
- // Result accessors, CriticalNatives
- {"nGetLineCount", "(J)I", (void*)nGetLineCount},
- {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
- {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
- {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
- {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
- {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
- {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
+ // Result accessors, CriticalNatives
+ {"nGetLineCount", "(J)I", (void*)nGetLineCount},
+ {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
+ {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
+ {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
+ {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
+ {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
+ {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
};
int register_android_graphics_text_LineBreaker(JNIEnv* env) {
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..746745a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
#include "GraphicsJNI.h"
#include "utils/misc.h"
#include "utils/Log.h"
@@ -65,13 +62,14 @@
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
- jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
- jboolean isRtl) {
+ jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+ jint start, jint end, jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
toBuilder(builderPtr)
- ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
+ ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+ isRtl);
}
// Regular JNI
@@ -84,13 +82,14 @@
// Regular JNI
static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr,
jcharArray javaText, jboolean computeHyphenation,
- jboolean computeLayout, jboolean fastHyphenationMode) {
+ jboolean computeLayout, jboolean computeBounds,
+ jboolean fastHyphenationMode) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
return toJLong(toBuilder(builderPtr)
- ->build(textBuffer, computeHyphenation, computeLayout,
+ ->build(textBuffer, computeHyphenation, computeLayout, computeBounds,
fastHyphenationMode, toMeasuredParagraph(hintPtr))
.release());
}
@@ -161,9 +160,9 @@
static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*)nInitBuilder},
- {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+ {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
- {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
+ {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
{"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
};
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8e4dd53..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
@@ -62,7 +59,7 @@
const minikin::Font* font = layout.getFont(i);
if (seenFonts.find(font) != seenFonts.end()) continue;
minikin::MinikinExtent extent = {};
- font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
+ layout.typeface(i)->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
overallAscent = std::min(overallAscent, extent.ascent);
overallDescent = std::max(overallDescent, extent.descent);
}
@@ -148,6 +145,30 @@
}
// CriticalNative
+static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).isFakeBold();
+}
+
+// CriticalNative
+static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).isFakeItalic();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).wghtAdjustment();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).italAdjustment();
+}
+
+// CriticalNative
static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i);
@@ -185,15 +206,19 @@
};
static const JNINativeMethod gResultMethods[] = {
- { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
- { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
- { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
- { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
- { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
- { "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
- { "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
- { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
- { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
+ {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount},
+ {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance},
+ {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent},
+ {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent},
+ {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId},
+ {"nGetX", "(JI)F", (void*)TextShaper_Result_getX},
+ {"nGetY", "(JI)F", (void*)TextShaper_Result_getY},
+ {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont},
+ {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold},
+ {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic},
+ {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride},
+ {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride},
+ {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc},
};
int register_android_graphics_text_TextShaper(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
index ffad699..e81cbfb 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -21,6 +21,9 @@
#include "RenderNode.h"
#include "RenderNodeDrawable.h"
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
namespace android {
namespace uirenderer {
@@ -72,9 +75,17 @@
}
auto imageSubset = mImageSubset.roundOut();
- backdropImage =
- backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,
- imageSubset, &mOutSubset, &mOutOffset);
+#ifdef __ANDROID__
+ if (canvas->recordingContext()) {
+ backdropImage =
+ SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter,
+ imageSubset, imageSubset, &mOutSubset, &mOutOffset);
+ } else
+#endif
+ {
+ backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset,
+ imageSubset, &mOutSubset, &mOutOffset);
+ }
canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
SkCanvas::kStrict_SrcRectConstraint);
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 8d5967b..5d3fb30 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -21,10 +21,15 @@
#include "GrBackendSurface.h"
#include "RenderNode.h"
#include "SkAndroidFrameworkUtils.h"
+#include "SkCanvas.h"
+#include "SkCanvasAndroid.h"
#include "SkClipStack.h"
#include "SkRect.h"
#include "SkM44.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include "utils/GLUtils.h"
#include <effects/GainmapRenderer.h>
#include "renderthread/CanvasContext.h"
@@ -34,7 +39,7 @@
namespace skiapipeline {
static void setScissor(int viewportHeight, const SkIRect& clip) {
- SkASSERT(!clip.isEmpty());
+ LOG_FATAL_IF(clip.isEmpty(), "empty scissor clip");
// transform to Y-flipped GL space, and prevent negatives
GLint y = viewportHeight - clip.fBottom;
GLint height = (viewportHeight - clip.fTop) - y;
@@ -42,9 +47,9 @@
}
static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
- GrBackendRenderTarget renderTarget = canvas->topLayerBackendRenderTarget();
+ GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
GrGLFramebufferInfo fboInfo;
- LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
+ LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo),
"getGLFrameBufferInfo failed");
*outFboID = fboInfo.fFBOID;
@@ -76,13 +81,13 @@
}
// flush will create a GrRenderTarget if not already present.
- canvas->flush();
+ directContext->flushAndSubmit();
GLuint fboID = 0;
SkISize fboSize;
GetFboDetails(canvas, &fboID, &fboSize);
- SkIRect surfaceBounds = canvas->topLayerBounds();
+ SkIRect surfaceBounds = skgpu::ganesh::TopLayerBounds(canvas);
SkIRect clipBounds = canvas->getDeviceClipBounds();
SkM44 mat4(canvas->getLocalToDevice());
SkRegion clipRegion;
@@ -95,12 +100,14 @@
SkImageInfo surfaceInfo =
canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
tmpSurface =
- SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
+ SkSurfaces::RenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
GrGLFramebufferInfo fboInfo;
- if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess)
- .getGLFramebufferInfo(&fboInfo)) {
+ if (!GrBackendRenderTargets::GetGLFramebufferInfo(
+ SkSurfaces::GetBackendRenderTarget(
+ tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite),
+ &fboInfo)) {
ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
return;
}
@@ -163,7 +170,7 @@
// GL ops get inserted here if previous flush is missing, which could dirty the stencil
bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
- tmpCanvas->flush(); // need this flush for the single op that draws into the stencil
+ directContext->flushAndSubmit(); // need this flush for the single op that draws into the stencil
// ensure that the framebuffer that the webview will render into is bound before after we
// draw into the stencil
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 9d72c23..ffa915a 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -15,21 +15,25 @@
*/
#include "RenderNodeDrawable.h"
+
#include <SkPaint.h>
#include <SkPaintFilterCanvas.h>
#include <SkPoint.h>
#include <SkRRect.h>
#include <SkRect.h>
#include <gui/TraceUtils.h>
+#include <include/effects/SkImageFilters.h>
+#ifdef __ANDROID__
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#endif
+
+#include <optional>
+
#include "RenderNode.h"
#include "SkiaDisplayList.h"
#include "StretchMask.h"
#include "TransformCanvas.h"
-#include <include/effects/SkImageFilters.h>
-
-#include <optional>
-
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -52,6 +56,7 @@
int nestLevel) const {
LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
for (auto& child : displayList.mChildNodes) {
+ if (!child.getRenderNode()->isRenderable()) continue;
const RenderProperties& childProperties = child.getNodeProperties();
// immediate children cannot be projected on their parent
@@ -255,9 +260,19 @@
snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
if (imageFilter) {
auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
- snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
- subset, clipBounds.roundOut(),
- &srcBounds, &offset);
+
+#ifdef __ANDROID__
+ if (recordingContext) {
+ snapshotImage = SkImages::MakeWithFilter(
+ recordingContext, snapshotImage, imageFilter, subset,
+ clipBounds.roundOut(), &srcBounds, &offset);
+ } else
+#endif
+ {
+ snapshotImage = SkImages::MakeWithFilter(snapshotImage, imageFilter, subset,
+ clipBounds.roundOut(), &srcBounds,
+ &offset);
+ }
}
} else {
const auto snapshotResult = renderNode->updateSnapshotIfRequired(
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 11977bd..136740c 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -27,7 +27,6 @@
#include <SkRect.h>
#include <SkScalar.h>
#include <SkShadowUtils.h>
-#include <include/private/SkShadowFlags.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 7495550..6ccb212 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -26,6 +26,7 @@
#include <string>
#include <vector>
+class GrDirectContext;
class SkData;
namespace android {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index af2d3b3..5c8285a 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -66,6 +66,12 @@
}
}
+void SkiaDisplayList::visit(std::function<void(const RenderNode&)> func) const {
+ for (auto& child : mChildNodes) {
+ child.getRenderNode()->visit(func);
+ }
+}
+
static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) {
Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0},
Vector3 {bounds.fRight, bounds.fTop, 0},
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 7af31a4..b9dc1c4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -96,6 +96,8 @@
bool hasText() const { return mDisplayList.hasText(); }
+ bool hasFill() const { return mDisplayList.hasFill(); }
+
/**
* Attempts to reset and reuse this DisplayList.
*
@@ -145,6 +147,8 @@
*/
void updateChildren(std::function<void(RenderNode*)> updateFn);
+ void visit(std::function<void(const RenderNode&)> func) const;
+
/**
* Returns true if there is a child render node that is a projection receiver.
*/
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 1042703..814b682 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@
, mTotalSize("bytes", 0)
, mPurgeableSize("bytes", 0) {}
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
for (auto& resource : mResourceMap) {
- if (SkStrContains(resourceName, resource.first)) {
+ if (resourceName.find(resource.first) != std::string::npos) {
return resource.second;
}
}
- return nullptr;
+ return std::nullopt;
}
void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@
}
// find the type if one exists
- const char* type;
+ std::string type;
auto typeResult = mCurrentValues.find("type");
if (typeResult != mCurrentValues.end()) {
type = typeResult->second.units;
@@ -71,14 +71,13 @@
}
// compute the type if we are itemizing or use the default "size" if we are not
- const char* key = (mItemizeType) ? type : sizeResult->first;
- SkASSERT(key != nullptr);
+ std::string key = (mItemizeType) ? type : sizeResult->first;
// compute the top level element name using either the map or category key
- const char* resourceName = mapName(mCurrentElement.c_str());
- if (mCategoryKey != nullptr) {
+ std::optional<std::string> resourceName = mapName(mCurrentElement);
+ if (mCategoryKey) {
// find the category if one exists
- auto categoryResult = mCurrentValues.find(mCategoryKey);
+ auto categoryResult = mCurrentValues.find(*mCategoryKey);
if (categoryResult != mCurrentValues.end()) {
resourceName = categoryResult->second.units;
} else if (mItemizeType) {
@@ -87,11 +86,11 @@
}
// if we don't have a pretty name then use the dumpName
- if (resourceName == nullptr) {
- resourceName = mCurrentElement.c_str();
+ if (!resourceName) {
+ resourceName = mCurrentElement;
}
- auto result = mResults.find(resourceName);
+ auto result = mResults.find(*resourceName);
if (result != mResults.end()) {
auto& resourceValues = result->second;
typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@
TraceValue sizeValue = sizeResult->second;
mCurrentValues.clear();
mCurrentValues.insert({key, sizeValue});
- mResults.insert({resourceName, mCurrentValues});
+ mResults.insert({*resourceName, mCurrentValues});
}
}
@@ -139,8 +138,9 @@
for (const auto& typedValue : namedItem.second) {
TraceValue traceValue = convertUnits(typedValue.second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
- log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
- traceValue.units, traceValue.count, entry);
+ log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
} else {
auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
}
}
@@ -156,7 +157,7 @@
size_t SkiaMemoryTracer::total() {
processElement();
- if (!strcmp("bytes", mTotalSize.units)) {
+ if ("bytes" == mTotalSize.units) {
return mTotalSize.value;
}
return 0;
@@ -166,16 +167,16 @@
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if ("bytes" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if ("KB" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b04..dbfc86b 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
#pragma once
-#include <SkString.h>
#include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
#include <utils/String8.h>
#include <unordered_map>
#include <vector>
@@ -60,17 +61,17 @@
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ std::string units;
float value;
int count;
};
- const char* mapName(const char* resourceName);
+ std::optional<std::string> mapName(const std::string& resourceName);
void processElement();
TraceValue convertUnits(const TraceValue& value);
const std::vector<ResourcePair> mResourceMap;
- const char* mCategoryKey = nullptr;
+ std::optional<std::string> mCategoryKey;
const bool mItemizeType;
// variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@
// variables storing information on the current node being dumped
std::string mCurrentElement;
- std::unordered_map<const char*, TraceValue> mCurrentValues;
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
// variable that stores the final format of the data after the individual elements are processed
- std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+ std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 56f1e64..c8d5987 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -16,6 +16,9 @@
#include "SkiaOpenGLPipeline.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
@@ -138,7 +141,8 @@
LOG_ALWAYS_FATAL("Unsupported color type.");
}
- GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
+ auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0,
+ STENCIL_BUFFER_SIZE, fboInfo);
SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
kUnknown_SkPixelGeometry);
@@ -150,9 +154,9 @@
surface = getBufferSkSurface(bufferParams);
preTransform = bufferParams.getTransform();
} else {
- surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
- getSurfaceOrigin(), colorType,
- mSurfaceColorSpace, &props);
+ surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+ getSurfaceOrigin(), colorType,
+ mSurfaceColorSpace, &props);
preTransform = SkMatrix::I();
}
@@ -175,7 +179,7 @@
{
ATRACE_NAME("flush commands");
- surface->flushAndSubmit();
+ skgpu::ganesh::FlushAndSubmit(surface);
}
layerUpdateQueue->clear();
@@ -184,11 +188,12 @@
dumpResourceCacheUsage();
}
- return {true, IRenderPipeline::DrawResult::kUnknownTime};
+ return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
}
-bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) {
GL_CHECKPOINT(LOW);
// Even if we decided to cancel the frame, from the perspective of jank
@@ -199,7 +204,7 @@
return false;
}
- *requireSwap = drew || mEglManager.damageRequiresSwap();
+ *requireSwap = drawResult.success || mEglManager.damageRequiresSwap();
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
return false;
@@ -246,8 +251,7 @@
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
- const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
- ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
+ mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
return true;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 0325593..ebe8b6e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -45,8 +45,9 @@
const renderthread::HardwareBufferRenderParams& bufferParams,
std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
- bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
[[nodiscard]] android::base::unique_fd flush() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index cb23bcc..e0f1f6e 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -16,14 +16,16 @@
#include "SkiaPipeline.h"
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
-#include <SkImageEncoder.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
-#include <SkImagePriv.h>
#include <SkMatrix.h>
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
@@ -75,7 +77,7 @@
return false;
}
for (SkImage* image : mutableImages) {
- if (SkImage_pinAsTexture(image, mRenderThread.getGrContext())) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
mPinnedImages.emplace_back(sk_ref_sp(image));
} else {
return false;
@@ -86,7 +88,7 @@
void SkiaPipeline::unpinImages() {
for (auto& image : mPinnedImages) {
- SkImage_unpinAsTexture(image.get(), mRenderThread.getGrContext());
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
}
mPinnedImages.clear();
}
@@ -187,9 +189,9 @@
kPremul_SkAlphaType, getSurfaceColorSpace());
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
if (node->getLayerSurface()) {
// update the transform in window of the layer to reset its origin wrt light source
// position
@@ -222,8 +224,8 @@
ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
auto image = bitmap->makeImage();
if (image.get()) {
- SkImage_pinAsTexture(image.get(), context);
- SkImage_unpinAsTexture(image.get(), context);
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
// A submit is necessary as there may not be a frame coming soon, so without a call
// to submit these texture uploads can just sit in the queue building up until
// we run out of RAM
@@ -439,6 +441,13 @@
procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
};
+ procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> {
+ GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx);
+ return SkPngEncoder::Encode(dCtx,
+ img,
+ SkPngEncoder::Options{});
+ };
+ procs.fImageCtx = mRenderThread.getGrContext();
auto data = picture->serialize(&procs);
savePictureAsync(data, mCapturedFile);
mCaptureSequence = 0;
@@ -621,7 +630,7 @@
auto bufferColorSpace = bufferParams.getColorSpace();
if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
!SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
bufferColorSpace, nullptr, true);
mBufferColorSpace = bufferColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 58c14c1..e917f9a 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -236,6 +236,17 @@
}
}
+void SkiaRecordingCanvas::onFilterPaint(android::Paint& paint) {
+ INHERITED::onFilterPaint(paint);
+ SkShader* shader = paint.getShader();
+ // TODO(b/264559422): This only works for very specifically a BitmapShader.
+ // It's better than nothing, though
+ SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
+ if (image) {
+ mDisplayList->mMutableImages.push_back(image);
+ }
+}
+
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
auto payload = DrawImagePayload(bitmap);
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index a8e4580..3bd091d 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -105,6 +105,8 @@
void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload);
+ void onFilterPaint(Paint& paint) override;
+
using INHERITED = SkiaCanvas;
};
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 7d19232..fd0a8e0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -87,7 +87,7 @@
}
if (backBuffer.get() == nullptr) {
- return {false, -1};
+ return {false, -1, android::base::unique_fd{}};
}
// update the coordinates of the global light position based on surface rotation
@@ -106,15 +106,15 @@
std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = backBuffer->getCanvas();
SkAutoCanvasRestore saver(profileCanvas, true);
- profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+ profileCanvas->concat(preTransform);
SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
- nsecs_t submissionTime = IRenderPipeline::DrawResult::kUnknownTime;
+ VulkanManager::VkDrawResult drawResult;
{
ATRACE_NAME("flush commands");
- submissionTime = vulkanManager().finishFrame(backBuffer.get());
+ drawResult = vulkanManager().finishFrame(backBuffer.get());
}
layerUpdateQueue->clear();
@@ -123,11 +123,12 @@
dumpResourceCacheUsage();
}
- return {true, submissionTime};
+ return {true, drawResult.submissionTime, std::move(drawResult.presentFence)};
}
-bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) {
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
@@ -136,10 +137,10 @@
return false;
}
- *requireSwap = drew;
+ *requireSwap = drawResult.success;
if (*requireSwap) {
- vulkanManager().swapBuffers(mVkSurface, screenDirty);
+ vulkanManager().swapBuffers(mVkSurface, screenDirty, std::move(drawResult.presentFence));
}
return *requireSwap;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 37b86f1..624eaa5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -45,8 +45,9 @@
const renderthread::HardwareBufferRenderParams& bufferParams,
std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
- bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
[[nodiscard]] android::base::unique_fd flush() override;
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index cad3703..1676787 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -18,14 +18,13 @@
#include "SkBlendMode.h"
#include "SkCanvas.h"
#include "SkSurface.h"
-#include "include/gpu/GpuTypes.h" // from Skia
#include "TransformCanvas.h"
#include "SkiaDisplayList.h"
using android::uirenderer::StretchMask;
-void StretchMask::draw(GrRecordingContext* context,
+void StretchMask::draw(GrRecordingContext*,
const StretchEffect& stretch,
const SkRect& bounds,
skiapipeline::SkiaDisplayList* displayList,
@@ -35,16 +34,14 @@
if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
mMaskSurface->height() != height) {
// Create a new surface if we don't have one or our existing size does
- // not match.
- mMaskSurface = SkSurface::MakeRenderTarget(
- context,
- skgpu::Budgeted::kYes,
- SkImageInfo::Make(
- width,
- height,
- SkColorType::kAlpha_8_SkColorType,
- SkAlphaType::kPremul_SkAlphaType)
- );
+ // not match. SkCanvas::makeSurface returns a new surface that will
+ // be GPU-backed if canvas was also.
+ mMaskSurface = canvas->makeSurface(SkImageInfo::Make(
+ width,
+ height,
+ SkColorType::kAlpha_8_SkColorType,
+ SkAlphaType::kPremul_SkAlphaType
+ ));
mIsDirty = true;
}
@@ -53,7 +50,7 @@
// Make sure to apply target transformation to the mask canvas
// to ensure the replayed drawing commands generate the same result
auto previousMatrix = displayList->mParentMatrix;
- displayList->mParentMatrix = maskCanvas->getTotalMatrix();
+ displayList->mParentMatrix = maskCanvas->getLocalToDeviceAs3x3();
maskCanvas->save();
maskCanvas->drawColor(0, SkBlendMode::kClear);
TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index adf3c06..475b110 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -35,6 +35,8 @@
#include "effects/GainmapRenderer.h"
#include <SkBlendMode.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
namespace android {
namespace uirenderer {
@@ -183,9 +185,9 @@
// drawing into the offscreen surface, so we need to reset it here.
canvas->resetMatrix();
- auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType,
- canvas->imageInfo().refColorSpace(),
- kBottomLeft_GrSurfaceOrigin);
+ auto functorImage = SkImages::DeferredFromAHardwareBuffer(
+ mFrameBuffer.get(), kPremul_SkAlphaType, canvas->imageInfo().refColorSpace(),
+ kBottomLeft_GrSurfaceOrigin);
canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint);
canvas->restore();
}
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
namespace uirenderer {
/**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
*/
struct DrawGlInfo {
// Input: current clip rect
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index f695556..30d4612 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -17,6 +17,7 @@
#include "CacheManager.h"
#include <GrContextOptions.h>
+#include <GrTypes.h>
#include <SkExecutor.h>
#include <SkGraphics.h>
#include <math.h>
@@ -110,13 +111,18 @@
contextOptions->fPersistentCache = &cache;
}
+static GrPurgeResourceOptions toSkiaEnum(bool scratchOnly) {
+ return scratchOnly ? GrPurgeResourceOptions::kScratchResourcesOnly :
+ GrPurgeResourceOptions::kAllResources;
+}
+
void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
// flush and submit all work to the gpu and wait for it to finish
- mGrContext->flushAndSubmit(/*syncCpu=*/true);
+ mGrContext->flushAndSubmit(GrSyncCpu::kYes);
switch (mode) {
case TrimLevel::BACKGROUND:
@@ -130,7 +136,7 @@
// that have persistent data to be purged in LRU order.
mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
break;
@@ -150,7 +156,7 @@
case CacheTrimLevel::ALL_CACHES:
SkGraphics::PurgeAllCaches();
if (mGrContext) {
- mGrContext->purgeUnlockedResources(false);
+ mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
}
break;
default:
@@ -163,7 +169,8 @@
return;
}
mGrContext->flushAndSubmit();
- mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
+ mGrContext->performDeferredCleanup(std::chrono::seconds(30),
+ GrPurgeResourceOptions::kAllResources);
}
void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -282,9 +289,10 @@
const nsecs_t frameCompleteNanos = mFrameCompletions[0];
const nsecs_t frameDiffNanos = now - frameCompleteNanos;
const nsecs_t cleanupMillis =
- ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
+ mMemoryPolicy.maximumResourceRetention));
mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
- mMemoryPolicy.purgeScratchOnly);
+ toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
}
}
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 5e43ac2..bcfa4f3 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -64,12 +64,13 @@
void unregisterCanvasContext(CanvasContext* context);
void onContextStopped(CanvasContext* context);
+ bool areAllContextsStopped();
+
private:
friend class RenderThread;
explicit CacheManager(RenderThread& thread);
void setupCacheLimits();
- bool areAllContextsStopped();
void checkUiHidden();
void scheduleDestroyContext();
void cancelDestroyContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f690783..9c7f7cc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -123,8 +123,9 @@
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline))
- , mHintSessionWrapper(uiThreadId, renderThreadId) {
+ , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) {
mRenderThread.cacheManager().registerCanvasContext(this);
+ mRenderThread.renderState().registerContextCallback(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -137,6 +138,8 @@
}
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
+ mRenderThread.renderState().removeContextCallback(this);
+ mHintSessionWrapper->destroy();
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -160,6 +163,7 @@
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
+ mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper);
}
static void setBufferCount(ANativeWindow* window) {
@@ -356,8 +360,9 @@
return true;
}
-static bool wasSkipped(FrameInfo* info) {
- return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
+static std::optional<SkippedFrameReason> wasSkipped(FrameInfo* info) {
+ if (info) return info->getSkippedFrameReason();
+ return std::nullopt;
}
bool CanvasContext::isSwapChainStuffed() {
@@ -406,13 +411,26 @@
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
- if (wasSkipped(mCurrentFrameInfo)) {
+ if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
// Use the oldest skipped frame in case we skip more than a single frame
if (!mSkippedFrameInfo) {
- mSkippedFrameInfo.emplace();
- mSkippedFrameInfo->vsyncId =
- mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
- mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ switch (*reason) {
+ case SkippedFrameReason::AlreadyDrawn:
+ case SkippedFrameReason::NoBuffer:
+ case SkippedFrameReason::NoOutputTarget:
+ mSkippedFrameInfo.emplace();
+ mSkippedFrameInfo->vsyncId =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ mSkippedFrameInfo->startTime =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ break;
+ case SkippedFrameReason::DrawingOff:
+ case SkippedFrameReason::ContextIsStopped:
+ case SkippedFrameReason::NothingToDraw:
+ // Do not report those as skipped frames as there was no frame expected to be
+ // drawn
+ break;
+ }
}
} else {
mCurrentFrameInfo = mJankTracker.startFrame();
@@ -426,7 +444,7 @@
info.damageAccumulator = &mDamageAccumulator;
info.layerUpdateQueue = &mLayerUpdateQueue;
info.damageGenerationId = mDamageId++;
- info.out.canDrawThisFrame = true;
+ info.out.skippedFrameReason = std::nullopt;
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -446,8 +464,8 @@
mIsDirty = true;
if (CC_UNLIKELY(!hasOutputTarget())) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
return;
}
@@ -462,23 +480,23 @@
if (vsyncDelta < 2_ms) {
// Already drew for this vsync pulse, UI draw request missed
// the deadline for RT animations
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn;
}
} else {
- info.out.canDrawThisFrame = true;
+ info.out.skippedFrameReason = std::nullopt;
}
// TODO: Do we need to abort out if the backdrop is added but not ready? Should that even
// be an allowable combination?
if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) {
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw;
}
- if (info.out.canDrawThisFrame) {
+ if (!info.out.skippedFrameReason) {
int err = mNativeSurface->reserveNext();
if (err != OK) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err));
if (err != TIMED_OUT) {
// A timed out surface can still recover, but assume others are permanently dead.
@@ -487,11 +505,11 @@
}
}
} else {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
}
bool postedFrameCallback = false;
- if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
+ if (info.out.hasAnimations || info.out.skippedFrameReason) {
if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
info.out.requiresUiRedraw = true;
}
@@ -544,7 +562,11 @@
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
- LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+ if (grContext->isDeviceLost()) {
+ LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+ return;
+ }
+ LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
@@ -557,9 +579,20 @@
mSyncDelayDuration = 0;
mIdleDuration = 0;
- if (!Properties::isDrawingEnabled() ||
- (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+ const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> {
+ if (!Properties::isDrawingEnabled()) {
+ return SkippedFrameReason::DrawingOff;
+ }
+
+ if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
+ return SkippedFrameReason::NothingToDraw;
+ }
+
+ return std::nullopt;
+ }();
+ if (skippedFrameReason) {
+ mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
+
if (auto grContext = getGrContext()) {
// Submit to ensure that any texture uploads complete and Skia can
// free its staging buffers.
@@ -592,10 +625,9 @@
{
// FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
// or it can lead to memory corruption.
- drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
- &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()), mBufferParams,
- profilerLock());
+ drawResult = mRenderPipeline->draw(
+ frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+ mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
}
uint64_t frameCompleteNr = getFrameNumber();
@@ -627,8 +659,8 @@
bool didDraw = false;
int error = OK;
- bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
- mCurrentFrameInfo, &requireSwap);
+ bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty, mCurrentFrameInfo,
+ &requireSwap);
mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));
@@ -735,7 +767,7 @@
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
- mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+ mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
@@ -743,7 +775,7 @@
int64_t actualDuration = frameDuration -
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
- mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+ mHintSessionWrapper->reportActualWorkDuration(actualDuration);
}
mLastDequeueBufferDuration = dequeueBufferDuration;
@@ -886,10 +918,10 @@
}
void CanvasContext::prepareAndDraw(RenderNode* node) {
- ATRACE_CALL();
+ int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
+ ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
- int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
int64_t frameDeadline = mRenderThread.timeLord().lastFrameDeadline();
int64_t frameInterval = mRenderThread.timeLord().frameIntervalNanos();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -899,7 +931,7 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
- if (info.out.canDrawThisFrame) {
+ if (!info.out.skippedFrameReason) {
draw(info.out.solelyTextureViewUpdates);
} else {
// wait on fences so tasks don't overlap next frame
@@ -961,6 +993,10 @@
}
}
+void CanvasContext::onContextDestroyed() {
+ destroyHardwareResources();
+}
+
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}
@@ -1078,11 +1114,11 @@
}
void CanvasContext::sendLoadResetHint() {
- mHintSessionWrapper.sendLoadResetHint();
+ mHintSessionWrapper->sendLoadResetHint();
}
void CanvasContext::sendLoadIncreaseHint() {
- mHintSessionWrapper.sendLoadIncreaseHint();
+ mHintSessionWrapper->sendLoadIncreaseHint();
}
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
@@ -1090,7 +1126,7 @@
}
void CanvasContext::startHintSession() {
- mHintSessionWrapper.init();
+ mHintSessionWrapper->init();
}
bool CanvasContext::shouldDither() {
@@ -1099,6 +1135,12 @@
return self->mColorMode != ColorMode::Default;
}
+void CanvasContext::visitAllRenderNodes(std::function<void(const RenderNode&)> func) const {
+ for (auto node : mRenderNodes) {
+ node->visit(func);
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3978fbc..e2e3fa3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -43,8 +43,10 @@
#include "Lighting.h"
#include "ReliableSurface.h"
#include "RenderNode.h"
+#include "renderstate/RenderState.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
+#include "utils/ForceDark.h"
#include "utils/RingBuffer.h"
namespace android {
@@ -64,7 +66,7 @@
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
// TODO: Rename to Renderer or some other per-window, top-level manager
-class CanvasContext : public IFrameCallback {
+class CanvasContext : public IFrameCallback, public IGpuContextCallback {
public:
static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, pid_t uiThreadId,
@@ -154,6 +156,7 @@
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
+ void onContextDestroyed() override;
DeferredLayerUpdater* createTextureLayer();
@@ -193,11 +196,9 @@
mRenderPipeline->setPictureCapturedCallback(callback);
}
- void setForceDark(bool enable) { mUseForceDark = enable; }
+ void setForceDark(ForceDarkType type) { mForceDarkType = type; }
- bool useForceDark() {
- return mUseForceDark;
- }
+ ForceDarkType getForceDarkType() { return mForceDarkType; }
SkISize getNextFrameSize() const;
@@ -237,6 +238,8 @@
static bool shouldDither();
+ void visitAllRenderNodes(std::function<void(const RenderNode&)>) const;
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
@@ -318,7 +321,7 @@
nsecs_t mLastDropVsync = 0;
bool mOpaque;
- bool mUseForceDark = false;
+ ForceDarkType mForceDarkType = ForceDarkType::NONE;
LightInfo mLightInfo;
LightGeometry mLightGeometry = {{0, 0, 0}, 0};
@@ -340,8 +343,7 @@
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
- std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
- GUARDED_BY(mFrameInfoMutex);
+ std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
std::mutex mFrameInfoMutex;
std::set<RenderNode*> mPrefetchedLayers;
@@ -360,7 +362,7 @@
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
- HintSessionWrapper mHintSessionWrapper;
+ std::shared_ptr<HintSessionWrapper> mHintSessionWrapper;
nsecs_t mLastDequeueBufferDuration = 0;
nsecs_t mSyncDelayDuration = 0;
nsecs_t mIdleDuration = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 53b43ba..1b333bf 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -104,7 +104,7 @@
info.forceDrawFrame = mForceDrawFrame;
mForceDrawFrame = false;
canUnblockUiThread = syncFrameState(info);
- canDrawThisFrame = info.out.canDrawThisFrame;
+ canDrawThisFrame = !info.out.skippedFrameReason.has_value();
solelyTextureViewUpdates = info.out.solelyTextureViewUpdates;
if (mFrameCommitCallback) {
@@ -192,11 +192,12 @@
if (CC_UNLIKELY(!hasTarget || !canDraw)) {
if (!hasTarget) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
+ info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
} else {
// If we have a surface but can't draw we must be stopped
mSyncResult |= SyncResult::ContextIsStopped;
+ info.out.skippedFrameReason = SkippedFrameReason::ContextIsStopped;
}
- info.out.canDrawThisFrame = false;
}
if (info.out.hasAnimations) {
@@ -204,7 +205,7 @@
mSyncResult |= SyncResult::UIRedrawRequired;
}
}
- if (!info.out.canDrawThisFrame) {
+ if (info.out.skippedFrameReason) {
mSyncResult |= SyncResult::FrameDropped;
}
// If prepareTextures is false, we ran out of texture cache space
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index b34da51..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -24,6 +24,7 @@
#include <vector>
#include "../Properties.h"
+#include "RenderThread.h"
#include "thread/CommonPool.h"
using namespace std::chrono_literals;
@@ -62,24 +63,26 @@
}
void HintSessionWrapper::destroy() {
- if (mHintSessionFuture.valid()) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture.has_value()) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
}
if (mHintSession) {
mBinding->closeSession(mHintSession);
mSessionValid = true;
mHintSession = nullptr;
}
+ mResetsSinceLastReport = 0;
}
bool HintSessionWrapper::init() {
if (mHintSession != nullptr) return true;
-
// If we're waiting for the session
- if (mHintSessionFuture.valid()) {
+ if (mHintSessionFuture.has_value()) {
// If the session is here
- if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
if (mHintSession != nullptr) {
mSessionValid = true;
return true;
@@ -107,12 +110,13 @@
tids.push_back(mUiThreadId);
tids.push_back(mRenderThreadId);
- // Use a placeholder target value to initialize,
- // this will always be replaced elsewhere before it gets used
- int64_t defaultTargetDurationNanos = 16666667;
+ // Use the cached target value if there is one, otherwise use a default. This is to ensure
+ // the cached target and target in PowerHAL are consistent, and that it updates correctly
+ // whenever there is a change.
+ int64_t targetDurationNanos =
+ mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
- return mBinding->createSession(manager, tids.data(), tids.size(),
- defaultTargetDurationNanos);
+ return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
});
return false;
}
@@ -136,6 +140,7 @@
actualDurationNanos < kSanityCheckUpperBound) {
mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
}
+ mLastFrameNotification = systemTime();
}
void HintSessionWrapper::sendLoadResetHint() {
@@ -155,6 +160,27 @@
mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
}
+bool HintSessionWrapper::alive() {
+ return mHintSession != nullptr;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+ return mLastFrameNotification;
+}
+
+// Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr) {
+ nsecs_t lastUpdate = wrapperPtr->getLastUpdate();
+ rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable {
+ if (wrapper->getLastUpdate() == lastUpdate) {
+ wrapper->destroy();
+ }
+ // Ensure the shared_ptr is killed at the end of the method
+ wrapper = nullptr;
+ });
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index f8b876e..41891cd 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -19,6 +19,7 @@
#include <android/performance_hint.h>
#include <future>
+#include <optional>
#include "utils/TimeUtils.h"
@@ -27,6 +28,8 @@
namespace renderthread {
+class RenderThread;
+
class HintSessionWrapper {
public:
friend class HintSessionWrapperTests;
@@ -40,10 +43,15 @@
void sendLoadIncreaseHint();
bool init();
void destroy();
+ bool alive();
+ nsecs_t getLastUpdate();
+ void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr);
private:
APerformanceHintSession* mHintSession = nullptr;
- std::future<APerformanceHintSession*> mHintSessionFuture;
+ // This needs to work concurrently for testing
+ std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
int mResetsSinceLastReport = 0;
nsecs_t mLastFrameNotification = 0;
@@ -57,6 +65,7 @@
static constexpr nsecs_t kResetHintTimeout = 100_ms;
static constexpr int64_t kSanityCheckLowerBound = 100_us;
static constexpr int64_t kSanityCheckUpperBound = 10_s;
+ static constexpr int64_t kDefaultTargetDuration = 16666667;
// Allows easier stub when testing
class HintSessionBinding {
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 023c29a..b8c3a4d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -61,6 +61,7 @@
// submission occurred. -1 if this time is unknown.
static constexpr nsecs_t kUnknownTime = -1;
nsecs_t commandSubmissionTime = kUnknownTime;
+ android::base::unique_fd presentFence;
};
virtual DrawResult draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
@@ -69,8 +70,9 @@
FrameInfoVisualizer* profiler,
const HardwareBufferRenderParams& bufferParams,
std::mutex& profilerLock) = 0;
- virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
+ virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
[[nodiscard]] virtual android::base::unique_fd flush() = 0;
virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 6df34be..64d38b9 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -150,11 +150,11 @@
}
AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
- .usage = mUsage,
- .format = mFormat,
.width = 1,
.height = 1,
.layers = 1,
+ .format = mFormat,
+ .usage = mUsage,
.rfu0 = 0,
.rfu1 = 0,
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 224c878..eab3605 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,7 +16,13 @@
#include "RenderProxy.h"
+#include <SkBitmap.h>
+#include <SkImage.h>
+#include <SkPicture.h>
#include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <ui/GraphicBufferAllocator.h>
+
#include "DeferredLayerUpdater.h"
#include "DisplayList.h"
#include "Properties.h"
@@ -29,12 +35,6 @@
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
-#include <SkBitmap.h>
-#include <SkImage.h>
-#include <SkPicture.h>
-
-#include <pthread.h>
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -123,7 +123,7 @@
}
void RenderProxy::allocateBuffers() {
- mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
+ mRenderThread.queue().post([this]() { mContext->allocateBuffers(); });
}
bool RenderProxy::pause() {
@@ -136,15 +136,16 @@
void RenderProxy::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
mRenderThread.queue().post(
- [=]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
+ [=, this]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
}
void RenderProxy::setLightGeometry(const Vector3& lightCenter, float lightRadius) {
- mRenderThread.queue().post([=]() { mContext->setLightGeometry(lightCenter, lightRadius); });
+ mRenderThread.queue().post(
+ [=, this]() { mContext->setLightGeometry(lightCenter, lightRadius); });
}
void RenderProxy::setOpaque(bool opaque) {
- mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); });
+ mRenderThread.queue().post([=, this]() { mContext->setOpaque(opaque); });
}
float RenderProxy::setColorMode(ColorMode mode) {
@@ -152,9 +153,9 @@
// an async call since we already know the return value
if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
return mRenderThread.queue().runSync(
- [=]() -> float { return mContext->setColorMode(mode); });
+ [=, this]() -> float { return mContext->setColorMode(mode); });
} else {
- mRenderThread.queue().post([=]() { mContext->setColorMode(mode); });
+ mRenderThread.queue().post([=, this]() { mContext->setColorMode(mode); });
return 1.f;
}
}
@@ -179,7 +180,7 @@
// destroyCanvasAndSurface() needs a fence as when it returns the
// underlying BufferQueue is going to be released from under
// the render thread.
- mRenderThread.queue().runSync([=]() { mContext->destroy(); });
+ mRenderThread.queue().runSync([this]() { mContext->destroy(); });
}
void RenderProxy::destroyFunctor(int functor) {
@@ -300,7 +301,7 @@
}
void RenderProxy::resetProfileInfo() {
- mRenderThread.queue().runSync([=]() {
+ mRenderThread.queue().runSync([this]() {
std::lock_guard lock(mRenderThread.getJankDataMutex());
mContext->resetFrameStats();
});
@@ -323,6 +324,11 @@
}
});
}
+ if (!Properties::isolatedProcess) {
+ std::string grallocInfo;
+ GraphicBufferAllocator::getInstance().dump(grallocInfo);
+ dprintf(fd, "%s\n", grallocInfo.c_str());
+ }
}
void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -350,15 +356,15 @@
}
void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
- mRenderThread.queue().post([=]() { mContext->addRenderNode(node, placeFront); });
+ mRenderThread.queue().post([=, this]() { mContext->addRenderNode(node, placeFront); });
}
void RenderProxy::removeRenderNode(RenderNode* node) {
- mRenderThread.queue().post([=]() { mContext->removeRenderNode(node); });
+ mRenderThread.queue().post([=, this]() { mContext->removeRenderNode(node); });
}
void RenderProxy::drawRenderNode(RenderNode* node) {
- mRenderThread.queue().runSync([=]() { mContext->prepareAndDraw(node); });
+ mRenderThread.queue().runSync([=, this]() { mContext->prepareAndDraw(node); });
}
void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
@@ -412,8 +418,8 @@
});
}
-void RenderProxy::setForceDark(bool enable) {
- mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
+void RenderProxy::setForceDark(ForceDarkType type) {
+ mRenderThread.queue().post([this, type]() { mContext->setForceDark(type); });
}
void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 47c1b0c..f2d8e94 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -31,6 +31,7 @@
#include "DrawFrameTask.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
+#include "utils/ForceDark.h"
class SkBitmap;
class SkPicture;
@@ -142,7 +143,7 @@
void addFrameMetricsObserver(FrameMetricsObserver* observer);
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
- void setForceDark(bool enable);
+ void setForceDark(ForceDarkType type);
static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request);
static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 0dea941..623ee4e 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
@@ -149,7 +150,7 @@
ATRACE_FORMAT("queue mFrameCallbackTask to run after %.2fms",
toFloatMillis(runAt - SteadyClock::now()).count());
queue().postAt(toNsecs_t(runAt.time_since_epoch()).count(),
- [=]() { dispatchFrameCallbacks(); });
+ [this]() { dispatchFrameCallbacks(); });
}
}
@@ -286,7 +287,7 @@
auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
auto size = glesVersion ? strlen(glesVersion) : -1;
cacheManager().configureContext(&options, glesVersion, size);
- sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
+ sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options));
LOG_ALWAYS_FATAL_IF(!grContext.get());
setGrContext(grContext);
}
@@ -357,7 +358,15 @@
String8 cachesOutput;
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
- dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.c_str());
+ dprintf(fd, "\nPipeline=%s\n%s", pipelineToString(), cachesOutput.c_str());
+ for (auto&& context : mCacheManager->mCanvasContexts) {
+ context->visitAllRenderNodes([&](const RenderNode& node) {
+ if (node.isTextureView()) {
+ dprintf(fd, "TextureView: %dx%d\n", node.getWidth(), node.getHeight());
+ }
+ });
+ }
+ dprintf(fd, "\n");
}
void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 46698a6..d55d28d 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -24,6 +24,9 @@
#include <GrTypes.h>
#include <android/sync.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
@@ -33,9 +36,6 @@
#include "pipeline/skia/ShaderCache.h"
#include "renderstate/RenderState.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -386,25 +386,23 @@
}
void VulkanManager::initialize() {
- std::lock_guard _lock{mInitializeLock};
+ std::call_once(mInitFlag, [&] {
+ GET_PROC(EnumerateInstanceVersion);
+ uint32_t instanceVersion;
+ LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
+ LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
- if (mDevice != VK_NULL_HANDLE) {
- return;
- }
+ this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
- GET_PROC(EnumerateInstanceVersion);
- uint32_t instanceVersion;
- LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
- LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
- this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
+ if (Properties::enablePartialUpdates && Properties::useBufferAge) {
+ mSwapBehavior = SwapBehavior::BufferAge;
+ }
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
-
- if (Properties::enablePartialUpdates && Properties::useBufferAge) {
- mSwapBehavior = SwapBehavior::BufferAge;
- }
+ mInitialized = true;
+ });
}
static void onGrContextReleased(void* context) {
@@ -438,7 +436,7 @@
options.fContextDeleteContext = this;
options.fContextDeleteProc = onGrContextReleased;
- return GrDirectContext::MakeVulkan(backendContext, options);
+ return GrDirectContexts::MakeVulkan(backendContext, options);
}
VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
@@ -518,7 +516,7 @@
// The following flush blocks the GPU immediately instead of waiting for
// other drawing ops. It seems dequeue_fence is not respected otherwise.
// TODO: remove the flush after finding why backendSemaphore is not working.
- bufferInfo->skSurface->flushAndSubmit();
+ skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
}
}
}
@@ -529,128 +527,121 @@
return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge);
}
-struct DestroySemaphoreInfo {
+class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> {
PFN_vkDestroySemaphore mDestroyFunction;
VkDevice mDevice;
VkSemaphore mSemaphore;
- // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
- // (including by the GPU) and inside the VulkanManager. So we always start with two refs, one
- // owned by Skia and one owned by the VulkanManager. The refs are decremented each time
- // destroy_semaphore is called with this object. Skia will call destroy_semaphore once it is
- // done with the semaphore and the GPU has finished work on the semaphore. The VulkanManager
- // calls destroy_semaphore after sending the semaphore to Skia and exporting it if need be.
- int mRefs = 2;
+ GrBackendSemaphore mGrBackendSemaphore;
- DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
- VkSemaphore semaphore)
- : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {}
+ SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
+ VkSemaphore semaphore)
+ : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
+ mGrBackendSemaphore.initVulkan(semaphore);
+ }
+
+ ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
+
+ friend class LightRefBase<SharedSemaphoreInfo>;
+ friend class sp<SharedSemaphoreInfo>;
+
+public:
+ VkSemaphore semaphore() const { return mSemaphore; }
+
+ GrBackendSemaphore* grBackendSemaphore() { return &mGrBackendSemaphore; }
};
static void destroy_semaphore(void* context) {
- DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context);
- --info->mRefs;
- if (!info->mRefs) {
- info->mDestroyFunction(info->mDevice, info->mSemaphore, nullptr);
- delete info;
- }
+ SharedSemaphoreInfo* info = reinterpret_cast<SharedSemaphoreInfo*>(context);
+ info->decStrong(0);
}
-nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
+VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
ATRACE_NAME("Vulkan finish frame");
- ALOGE_IF(mSwapSemaphore != VK_NULL_HANDLE || mDestroySemaphoreContext != nullptr,
- "finishFrame already has an outstanding semaphore");
- VkExportSemaphoreCreateInfo exportInfo;
- exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
- exportInfo.pNext = nullptr;
- exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkSemaphoreCreateInfo semaphoreInfo;
- semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- semaphoreInfo.pNext = &exportInfo;
- semaphoreInfo.flags = 0;
- VkSemaphore semaphore;
- VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
-
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
-
+ sp<SharedSemaphoreInfo> sharedSemaphore;
GrFlushInfo flushInfo;
- if (err == VK_SUCCESS) {
- mDestroySemaphoreContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
- flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
- flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext = mDestroySemaphoreContext;
- } else {
- semaphore = VK_NULL_HANDLE;
- }
- GrSemaphoresSubmitted submitted =
- surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
- GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
- ALOGE_IF(!context, "Surface is not backed by gpu");
- context->submit();
- const nsecs_t submissionTime = systemTime();
- if (semaphore != VK_NULL_HANDLE) {
- if (submitted == GrSemaphoresSubmitted::kYes) {
- mSwapSemaphore = semaphore;
- if (mFrameBoundaryANDROID) {
- // retrieve VkImage used as render target
- VkImage image = VK_NULL_HANDLE;
- GrBackendRenderTarget backendRenderTarget =
- surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
- if (backendRenderTarget.isValid()) {
- GrVkImageInfo info;
- if (backendRenderTarget.getVkImageInfo(&info)) {
- image = info.fImage;
- } else {
- ALOGE("Frame boundary: backend is not vulkan");
- }
- } else {
- ALOGE("Frame boundary: invalid backend render target");
- }
- // frameBoundaryANDROID needs to know about mSwapSemaphore, but
- // it won't wait on it.
- mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
- }
- } else {
- destroy_semaphore(mDestroySemaphoreContext);
- mDestroySemaphoreContext = nullptr;
+
+ {
+ VkExportSemaphoreCreateInfo exportInfo;
+ exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+ exportInfo.pNext = nullptr;
+ exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+ VkSemaphoreCreateInfo semaphoreInfo;
+ semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ semaphoreInfo.pNext = &exportInfo;
+ semaphoreInfo.flags = 0;
+ VkSemaphore semaphore;
+ VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+ ALOGE_IF(VK_SUCCESS != err,
+ "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
+
+ if (err == VK_SUCCESS) {
+ sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
+ flushInfo.fNumSemaphores = 1;
+ flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
+ flushInfo.fFinishedProc = destroy_semaphore;
+ sharedSemaphore->incStrong(0);
+ flushInfo.fFinishedContext = sharedSemaphore.get();
}
}
+
+ GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
+ ALOGE_IF(!context, "Surface is not backed by gpu");
+ GrSemaphoresSubmitted submitted = context->flush(
+ surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
+ context->submit();
+ VkDrawResult drawResult{
+ .submissionTime = systemTime(),
+ };
+ if (sharedSemaphore) {
+ if (submitted == GrSemaphoresSubmitted::kYes && mFrameBoundaryANDROID) {
+ // retrieve VkImage used as render target
+ VkImage image = VK_NULL_HANDLE;
+ GrBackendRenderTarget backendRenderTarget = SkSurfaces::GetBackendRenderTarget(
+ surface, SkSurfaces::BackendHandleAccess::kFlushRead);
+ if (backendRenderTarget.isValid()) {
+ GrVkImageInfo info;
+ if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) {
+ image = info.fImage;
+ } else {
+ ALOGE("Frame boundary: backend is not vulkan");
+ }
+ } else {
+ ALOGE("Frame boundary: invalid backend render target");
+ }
+ // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+ // it won't wait on it.
+ mFrameBoundaryANDROID(mDevice, sharedSemaphore->semaphore(), image);
+ }
+ VkSemaphoreGetFdInfoKHR getFdInfo;
+ getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+ getFdInfo.pNext = nullptr;
+ getFdInfo.semaphore = sharedSemaphore->semaphore();
+ getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+ int fenceFd = -1;
+ VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
+ ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
+ drawResult.presentFence.reset(fenceFd);
+ } else {
+ ALOGE("VulkanManager::finishFrame(): Semaphore submission failed");
+ mQueueWaitIdle(mGraphicsQueue);
+ }
+
skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
- return submissionTime;
+ return drawResult;
}
-void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
+void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+ android::base::unique_fd&& presentFence) {
if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
ATRACE_NAME("Finishing GPU work");
mDeviceWaitIdle(mDevice);
}
- int fenceFd = -1;
- if (mSwapSemaphore != VK_NULL_HANDLE) {
- VkSemaphoreGetFdInfoKHR getFdInfo;
- getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
- getFdInfo.pNext = nullptr;
- getFdInfo.semaphore = mSwapSemaphore;
- getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
- } else {
- ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
- mQueueWaitIdle(mGraphicsQueue);
- }
- if (mDestroySemaphoreContext) {
- destroy_semaphore(mDestroySemaphoreContext);
- }
-
- surface->presentCurrentBuffer(dirtyRect, fenceFd);
- mSwapSemaphore = VK_NULL_HANDLE;
- mDestroySemaphoreContext = nullptr;
+ surface->presentCurrentBuffer(dirtyRect, presentFence.release());
}
void VulkanManager::destroySurface(VulkanSurface* surface) {
@@ -751,25 +742,20 @@
return INVALID_OPERATION;
}
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
+ auto sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
- DestroySemaphoreInfo* destroyInfo =
- new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
// Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback
- // which will remove its ref to the semaphore. The VulkanManager must still release its ref,
- // when it is done with the semaphore.
GrFlushInfo flushInfo;
flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
+ flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext = destroyInfo;
+ sharedSemaphore->incStrong(0);
+ flushInfo.fFinishedContext = sharedSemaphore.get();
GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
grContext->submit();
if (submitted == GrSemaphoresSubmitted::kNo) {
ALOGE("VulkanManager::createReleaseFence: Failed to submit semaphore");
- destroy_semaphore(destroyInfo);
return INVALID_OPERATION;
}
@@ -782,7 +768,6 @@
int fenceFd = 0;
err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
- destroy_semaphore(destroyInfo);
if (VK_SUCCESS != err) {
ALOGE("VulkanManager::createReleaseFence: Failed to get semaphore Fd");
return INVALID_OPERATION;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 2be1ffd..b92ebb3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -22,6 +22,7 @@
#endif
#include <GrContextOptions.h>
#include <SkSurface.h>
+#include <android-base/unique_fd.h>
#include <utils/StrongPointer.h>
#include <vk/GrVkBackendContext.h>
#include <vk/GrVkExtensions.h>
@@ -70,7 +71,7 @@
void initialize();
// Quick check to see if the VulkanManager has been initialized.
- bool hasVkContext() { return mDevice != VK_NULL_HANDLE; }
+ bool hasVkContext() { return mInitialized; }
// Create and destroy functions for wrapping an ANativeWindow in a VulkanSurface
VulkanSurface* createSurface(ANativeWindow* window,
@@ -82,10 +83,17 @@
void destroySurface(VulkanSurface* surface);
Frame dequeueNextBuffer(VulkanSurface* surface);
+
+ struct VkDrawResult {
+ // The estimated start time for intiating GPU work, -1 if unknown.
+ nsecs_t submissionTime;
+ android::base::unique_fd presentFence;
+ };
+
// Finishes the frame and submits work to the GPU
- // Returns the estimated start time for intiating GPU work, -1 otherwise.
- nsecs_t finishFrame(SkSurface* surface);
- void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect);
+ VkDrawResult finishFrame(SkSurface* surface);
+ void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+ android::base::unique_fd&& presentFence);
// Inserts a wait on fence command into the Vulkan command buffer.
status_t fenceWait(int fence, GrDirectContext* grContext);
@@ -201,10 +209,8 @@
GrVkExtensions mExtensions;
uint32_t mDriverVersion = 0;
- VkSemaphore mSwapSemaphore = VK_NULL_HANDLE;
- void* mDestroySemaphoreContext = nullptr;
-
- std::mutex mInitializeLock;
+ std::once_flag mInitFlag;
+ std::atomic_bool mInitialized = false;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 3168cb0..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -16,6 +16,7 @@
#include "VulkanSurface.h"
+#include <include/android/SkSurfaceAndroid.h>
#include <GrDirectContext.h>
#include <SkSurface.h>
#include <algorithm>
@@ -24,9 +25,6 @@
#include "VulkanManager.h"
#include "utils/Color.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -470,12 +468,12 @@
surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
surfaceProps.pixelGeometry());
}
- bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
+ bufferInfo->skSurface = SkSurfaces::WrapAndroidHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
/*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
- ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
+ ALOGE("SkSurfaces::WrapAndroidHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
mNativeBuffers[idx].dequeue_fence.release());
mNativeBuffers[idx].dequeued = false;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4890ed..ad963dd 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,8 @@
#include "DeferredLayerUpdater.h"
#include "hwui/Paint.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
#include <minikin/Layout.h>
#include <pipeline/skia/SkiaOpenGLPipeline.h>
#include <pipeline/skia/SkiaVulkanPipeline.h>
@@ -179,5 +181,13 @@
return outlineInLocalCoord;
}
+SkFont TestUtils::defaultFont() {
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont =
+ Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+ SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
+ LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
+ return SkFont(sk_ref_sp(skTypeface));
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 81ecfe5..0ede902 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -30,6 +30,7 @@
#include <SkBitmap.h>
#include <SkColor.h>
+#include <SkFont.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
@@ -61,18 +62,10 @@
ADD_FAILURE() << "ClipState not a rect"; \
}
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
- TEST(test_case_name, test_name##_##pipeline) { \
- RenderPipelineType oldType = Properties::getRenderPipelineType(); \
- Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \
- functionCall; \
- Properties::overrideRenderPipelineType(oldType); \
- };
-
-#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
- INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \
- TestUtils::runOnRenderThread( \
- test_case_name##_##test_name##_RenderThreadTest::doTheThing))
+#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name) \
+ TEST(test_case_name, test_name) { \
+ TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+ }
/**
* Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
@@ -83,21 +76,7 @@
public: \
static void doTheThing(renderthread::RenderThread& renderThread); \
}; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
- /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
- renderthread::RenderThread& renderThread)
-
-/**
- * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
- */
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name); \
/* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
/* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
@@ -307,13 +286,21 @@
int destroyed = 0;
int removeOverlays = 0;
int glesDraw = 0;
+ int vkInitialize = 0;
+ int vkDraw = 0;
+ int vkPostDraw = 0;
};
static void expectOnRenderThread(const std::string_view& function = "unknown") {
EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function;
}
- static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) {
+ static int createMockFunctor() {
+ const auto renderMode = WebViewFunctor_queryPlatformRenderMode();
+ return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode);
+ }
+
+ static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) {
auto callbacks = WebViewFunctorCallbacks{
.onSync =
[](int functor, void* client_data, const WebViewSyncData& data) {
@@ -345,15 +332,30 @@
sMockFunctorCounts[functor].glesDraw++;
};
break;
- default:
- ADD_FAILURE();
- return WebViewFunctorCallbacks{};
+ case RenderMode::Vulkan:
+ callbacks.vk.initialize = [](int functor, void* data,
+ const VkFunctorInitParams& params) {
+ expectOnRenderThread("initialize");
+ sMockFunctorCounts[functor].vkInitialize++;
+ };
+ callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
+ const WebViewOverlayData& overlayParams) {
+ expectOnRenderThread("draw");
+ sMockFunctorCounts[functor].vkDraw++;
+ };
+ callbacks.vk.postDraw = [](int functor, void* data) {
+ expectOnRenderThread("postDraw");
+ sMockFunctorCounts[functor].vkPostDraw++;
+ };
+ break;
}
return callbacks;
}
static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
+ static SkFont defaultFont();
+
private:
static std::unordered_map<int, CallCounts> sMockFunctorCounts;
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4a5d946..97d4c82 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -57,7 +57,7 @@
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index bb95490..159541c 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -102,7 +102,7 @@
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb..e227999 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
* limitations under the License.
*/
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
#include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+
+#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
using namespace android;
+using namespace android::base;
using namespace android::uirenderer;
using namespace android::uirenderer::test;
@@ -69,6 +71,9 @@
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
+ --benchmark_list_tests Lists the tests that would run but does not run them
+ --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+ that doesn't match the given regex is run
--renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
--skip-leak-check Skips the memory leak check
--report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@
if (!strcmp(format, "tabular")) {
gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
} else if (!strcmp(format, "json")) {
+ // We cannot print the leak check if outputing to JSON as that will break
+ // JSON parsers since it's not JSON-formatted
+ gRunLeakCheck = false;
gBenchmarkReporter.reset(new benchmark::JSONReporter());
} else {
fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@
return true;
}
+static void addTestsThatMatchFilter(std::string spec) {
+ if (spec.empty() || spec == "all") {
+ spec = "."; // Regexp that matches all benchmarks
+ }
+ bool isNegativeFilter = false;
+ if (spec[0] == '-') {
+ spec.replace(0, 1, "");
+ isNegativeFilter = true;
+ }
+ std::regex re(spec, std::regex_constants::extended);
+ for (auto& iter : TestScene::testMap()) {
+ if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+ (!isNegativeFilter && std::regex_search(iter.first, re))) {
+ gRunTests.push_back(iter.second);
+ }
+ }
+}
+
// For options that only exist in long-form. Anything in the
// 0-255 range is reserved for short options (which just use their ASCII value)
namespace LongOpts {
@@ -170,6 +196,8 @@
ReportFrametime,
CpuSet,
BenchmarkFormat,
+ BenchmarkListTests,
+ BenchmarkFilter,
Onscreen,
Offscreen,
Renderer,
@@ -179,14 +207,16 @@
}
static const struct option LONG_OPTIONS[] = {
- {"frames", required_argument, nullptr, 'f'},
- {"repeat", required_argument, nullptr, 'r'},
+ {"count", required_argument, nullptr, 'c'},
+ {"runs", required_argument, nullptr, 'r'},
{"help", no_argument, nullptr, 'h'},
{"list", no_argument, nullptr, LongOpts::List},
{"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
{"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
{"cpuset", required_argument, nullptr, LongOpts::CpuSet},
{"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+ {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+ {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
{"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@
static const char* SHORT_OPTIONS = "c:r:h";
void parseOptions(int argc, char* argv[]) {
+ benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
int c;
bool error = false;
+ bool listTestsOnly = false;
+ bool testsAreFiltered = false;
opterr = 0;
while (true) {
@@ -272,6 +306,21 @@
}
break;
+ case LongOpts::BenchmarkListTests:
+ if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+ listTestsOnly = true;
+ }
+ break;
+
+ case LongOpts::BenchmarkFilter:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ addTestsThatMatchFilter(optarg);
+ testsAreFiltered = true;
+ break;
+
case LongOpts::Renderer:
if (!optarg) {
error = true;
@@ -346,11 +395,18 @@
}
}
} while (optind < argc);
- } else {
+ } else if (gRunTests.empty() && !testsAreFiltered) {
for (auto& iter : TestScene::testMap()) {
gRunTests.push_back(iter.second);
}
}
+
+ if (listTestsOnly) {
+ for (auto& iter : gRunTests) {
+ std::cout << iter.name << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+ }
}
int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 138b3efd..b8b3f0a 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -46,7 +46,7 @@
EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
- // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+ // SkImages::BorrowTextureFrom should fail if given null GrDirectContext.
textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index 2b90bda..89d00d3 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -20,7 +20,8 @@
#include "renderthread/EglManager.h"
#include "tests/common/TestUtils.h"
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
using namespace android;
@@ -34,7 +35,7 @@
}
// TOOD(258700630): fix this test and re-enable
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
+RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) {
int32_t width = DeviceInfo::get()->getWidth();
int32_t height = DeviceInfo::get()->getHeight();
GrDirectContext* grContext = renderThread.getGrContext();
@@ -46,8 +47,8 @@
while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
SkImageInfo info = SkImageInfo::MakeA8(width, height);
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes,
- info);
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kYes,
+ info);
surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
grContext->flushAndSubmit();
@@ -57,9 +58,8 @@
// create an image and pin it so that we have something with a unique key in the cache
sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height));
- sk_sp<SkImage> image = bitmap->makeImage();
- ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
-
+ sk_sp<SkImage> image = bitmap->makeImage(); // calls skgpu::ganesh::PinAsTexture under the hood.
+ ASSERT_TRUE(skgpu::ganesh::PinAsTexture(grContext, image.get()));
// attempt to trim all memory while we still hold strong refs
renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
@@ -71,7 +71,7 @@
}
// unpin the image which should add a unique purgeable key to the cache
- SkImage_unpinAsTexture(image.get(), grContext);
+ skgpu::ganesh::UnpinTexture(grContext, image.get());
// verify that we have enough purgeable bytes
const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 9e376e3..47a4105 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -19,6 +19,7 @@
#include "AnimationContext.h"
#include "IContextFactory.h"
#include "renderthread/CanvasContext.h"
+#include "renderthread/VulkanManager.h"
#include "tests/common/TestUtils.h"
using namespace android;
@@ -42,3 +43,38 @@
canvasContext->destroy();
}
+
+RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) {
+ auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc);
+ });
+ ASSERT_TRUE(node->isValid());
+ EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType());
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+
+ auto& cacheManager = renderThread.cacheManager();
+ EXPECT_TRUE(cacheManager.areAllContextsStopped());
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(
+ CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0));
+ canvasContext->buildLayer(node.get());
+ EXPECT_TRUE(node->hasLayer());
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ EXPECT_TRUE(instance->hasVkContext());
+ } else {
+ ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?";
+ }
+ }
+ renderThread.destroyRenderingContext();
+ EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed";
+
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ ADD_FAILURE() << "VulkanManager still exists";
+ EXPECT_FALSE(instance->hasVkContext());
+ }
+ }
+}
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 1f6edf3..18c5047 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -225,9 +225,9 @@
TEST(CanvasOp, simpleDrawRect) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
- buffer.push<Op::DrawRect> ({
- .paint = SkPaint{},
- .rect = SkRect::MakeEmpty()
+ buffer.push<Op::DrawRect>({
+ .rect = SkRect::MakeEmpty(),
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -242,9 +242,9 @@
EXPECT_EQ(buffer.size(), 0);
SkRegion region;
region.setRect(SkIRect::MakeWH(12, 50));
- buffer.push<Op::DrawRegion> ({
- .paint = SkPaint{},
- .region = region
+ buffer.push<Op::DrawRegion>({
+ .region = region,
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -264,9 +264,9 @@
clip.setRect(SkIRect::MakeWH(100, 100));
SkRegion region;
region.setPath(path, clip);
- buffer.push<Op::DrawRegion> ({
- .paint = SkPaint{},
- .region = region
+ buffer.push<Op::DrawRegion>({
+ .region = region,
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -279,11 +279,11 @@
TEST(CanvasOp, simpleDrawRoundRect) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
- buffer.push<Op::DrawRoundRect> ({
- .paint = SkPaint{},
- .rect = SkRect::MakeEmpty(),
- .rx = 10,
- .ry = 10
+ buffer.push<Op::DrawRoundRect>({
+ .rect = SkRect::MakeEmpty(),
+ .rx = 10,
+ .ry = 10,
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -611,9 +611,9 @@
EXPECT_EQ(0, canvas->sumTotalDrawCalls());
ImmediateModeRasterizer rasterizer{canvas};
- auto op = CanvasOp<Op::DrawRect> {
- .paint = SkPaint{},
- .rect = SkRect::MakeEmpty()
+ auto op = CanvasOp<Op::DrawRect>{
+ .rect = SkRect::MakeEmpty(),
+ .paint = SkPaint{},
};
EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>);
rasterizer.draw(op);
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 0c389bfe8..cfa18ae 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -40,7 +40,7 @@
// push the deferred updates to the layer
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
- sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
+ sk_sp<SkImage> layerImage = SkImages::RasterFromBitmap(bitmap);
layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty());
// the backing layer should now have all the properties applied.
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index 623be1e..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -23,9 +23,11 @@
#include <chrono>
#include "Properties.h"
+#include "tests/common/TestUtils.h"
using namespace testing;
using namespace std::chrono_literals;
+using namespace android::uirenderer::renderthread;
APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
@@ -42,6 +44,9 @@
protected:
std::shared_ptr<HintSessionWrapper> mWrapper;
+ std::promise<int> blockDestroyCallUntil;
+ std::promise<int> waitForDestroyFinished;
+
class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
public:
void init() override;
@@ -53,11 +58,17 @@
MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+ // Needs to be on the binding so it can be accessed from static methods
+ std::promise<int> allowCreationToFinish;
};
// Must be static so it can have function pointers we can point to with static methods
static std::shared_ptr<MockHintSessionBinding> sMockBinding;
+ static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); }
+ void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); }
+ void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); }
+
// Must be static so we can point to them as normal fn pointers with HintSessionBinding
static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
@@ -65,6 +76,12 @@
int64_t initialTarget) {
return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
}
+ static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager,
+ const int32_t* ids, size_t idsSize,
+ int64_t initialTarget) {
+ sMockBinding->allowCreationToFinish.get_future().wait();
+ return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+ }
static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
const int32_t* ids, size_t idsSize,
int64_t initialTarget) {
@@ -85,7 +102,21 @@
static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
sMockBinding->fakeSendHint(session, hintId);
};
- void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); }
+ void waitForWrapperReady() {
+ if (mWrapper->mHintSessionFuture.has_value()) {
+ mWrapper->mHintSessionFuture->wait();
+ }
+ }
+ void scheduleDelayedDestroyManaged() {
+ TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
+ // Guaranteed to be scheduled first, allows destruction to start
+ rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); });
+ // Guaranteed to be scheduled second, destroys the session
+ mWrapper->delayedDestroy(rt, 1_ms, mWrapper);
+ // This is guaranteed to be queued after the destroy, signals that destruction is done
+ rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); });
+ });
+ }
};
std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
@@ -113,6 +144,7 @@
}
void HintSessionWrapperTests::TearDown() {
+ // Ensure that anything running on RT is completely finished
mWrapper = nullptr;
sMockBinding = nullptr;
}
@@ -122,6 +154,7 @@
sMockBinding->createSession = stubSlowCreateSession;
mWrapper->init();
mWrapper = nullptr;
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
}
TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
@@ -148,4 +181,162 @@
mWrapper->sendLoadResetHint();
}
+TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to ensure the wrapper grabs the promise value
+ mWrapper->init();
+
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // Schedule delayed destruction, allow it to run, and check when it's done
+ scheduleDelayedDestroyManaged();
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+ // If we then delete the wrapper, it shouldn't close the session again
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0);
+ mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens after
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow destruction to happen first
+ allowDelayedDestructionToStart();
+
+ // Make sure destruction has had time to happen
+ std::this_thread::sleep_for(50ms);
+
+ // Then, allow creation to finish after delayed destroy runs
+ allowCreationToFinish();
+
+ // Wait for destruction to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens before
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow creation to happen first
+ allowCreationToFinish();
+
+ // Make sure creation has had time to happen
+ waitForWrapperReady();
+
+ // Then allow destruction to happen after creation is done
+ allowDelayedDestructionToStart();
+
+ // Wait for it to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+ EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, report an actual duration
+ mWrapper->reportActualWorkDuration(5_ms);
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ EXPECT_CALL(*sMockBinding,
+ fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+ .Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, send a load_up hint
+ mWrapper->sendLoadIncreaseHint();
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, kill the session
+ mWrapper->destroy();
+
+ // Verify it died
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+ // Then, run the delayed deletion after manually killing the session
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close again and is still dead
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
} // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index f67042b..073a835 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-#include <VectorDrawable.h>
-#include <gtest/gtest.h>
-
#include <SkBlendMode.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
+#include <VectorDrawable.h>
+#include <gtest/gtest.h>
#include <include/effects/SkImageFilters.h>
#include <string.h>
@@ -144,7 +143,7 @@
}
TEST(RenderNodeDrawable, composeOnLayer) {
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
@@ -155,7 +154,7 @@
});
// attach a layer to the render node
- auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surfaceLayer = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
auto canvas2 = surfaceLayer->getCanvas();
canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
rootNode->setLayerSurface(surfaceLayer);
@@ -190,7 +189,7 @@
}
TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) {
- auto surface = SkSurface::MakeRasterN32Premul(400, 800);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(400, 800));
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
@@ -356,7 +355,7 @@
EXPECT_EQ(3, canvas.getIndex());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) {
class ProjectionTestCanvas : public SkCanvas {
public:
ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
@@ -420,7 +419,7 @@
EXPECT_EQ(2, canvas.getDrawCounter());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
/* R is backward projected on B and C is a layer.
A
/ \
@@ -1053,7 +1052,7 @@
}
// Verify that layers are composed with linear filtering.
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
+RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) {
static const int CANVAS_WIDTH = 1;
static const int CANVAS_HEIGHT = 1;
static const int LAYER_WIDTH = 1;
@@ -1077,7 +1076,8 @@
});
layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT));
+ layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(LAYER_WIDTH,
+ LAYER_HEIGHT)));
FrameTestCanvas canvas;
RenderNodeDrawable drawable(layerNode.get(), &canvas, true);
@@ -1170,7 +1170,7 @@
}
// Draw a vector drawable twice but with different bounds and verify correct bounds are used.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
+RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
static const int CANVAS_WIDTH = 100;
static const int CANVAS_HEIGHT = 200;
class VectorDrawableTestCanvas : public TestCanvasBase {
@@ -1243,7 +1243,7 @@
SkBitmap bitmap;
bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT);
bitmap.setImmutable();
- return SkImage::MakeFromBitmap(bitmap);
+ return bitmap.asImage();
}
SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); }
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 80796f4..e727ea8 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -231,8 +231,7 @@
}
TEST(RenderNode, releasedCallback) {
- int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor = TestUtils::createMockFunctor();
auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) {
canvas.drawWebViewFunctor(functor);
@@ -332,3 +331,31 @@
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
canvasContext->destroy();
}
+
+TEST(RenderNode, hasNoFill) {
+ auto rootNode =
+ TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ Paint paint;
+ paint.setStyle(SkPaint::Style::kStroke_Style);
+ canvas.drawRect(10, 10, 100, 100, paint);
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+ EXPECT_FALSE(rootNode.get()->getDisplayList().hasFill());
+ EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
+
+TEST(RenderNode, hasFill) {
+ auto rootNode =
+ TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ Paint paint;
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas.drawRect(10, 10, 100, 100, paint);
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+ EXPECT_TRUE(rootNode.get()->getDisplayList().hasFill());
+ EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 9aa2e1d..0f8bd13 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -370,9 +370,9 @@
}
using namespace android::uirenderer;
-RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
- // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+ // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants.
GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
}
if (!folderExist(getExternalStorageFolder())) {
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 87c5216..e53fcaa 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,7 @@
using namespace android::uirenderer;
TEST(SkiaCanvas, drawShadowLayer) {
- auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
SkiaCanvas canvas(surface->getCanvas());
// clear to white
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index f825d7c..064d42e 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -48,8 +48,7 @@
SkCanvas dummyCanvas;
RenderNodeDrawable drawable(nullptr, &dummyCanvas);
skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
WebViewFunctor_release(functor1);
skiaDL->mChildFunctors.push_back(&functorDrawable);
@@ -101,8 +100,7 @@
SkCanvas dummyCanvas;
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
auto& counts = TestUtils::countsForFunctor(functor1);
skiaDL.mChildFunctors.push_back(
skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
@@ -131,6 +129,33 @@
EXPECT_EQ(counts.destroyed, 1);
}
+TEST(SkiaDisplayList, recordMutableBitmap) {
+ SkiaRecordingCanvas canvas{nullptr, 100, 100};
+ auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+ EXPECT_FALSE(bitmap->isImmutable());
+ canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+ auto displayList = canvas.finishRecording();
+ ASSERT_EQ(1, displayList->mMutableImages.size());
+ EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+ EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
+TEST(SkiaDisplayList, recordMutableBitmapInShader) {
+ SkiaRecordingCanvas canvas{nullptr, 100, 100};
+ auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+ EXPECT_FALSE(bitmap->isImmutable());
+ SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+ Paint paint;
+ paint.setShader(bitmap->makeImage()->makeShader(sampling));
+ canvas.drawPaint(paint);
+ auto displayList = canvas.finishRecording();
+ ASSERT_EQ(1, displayList->mMutableImages.size());
+ EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+ EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
class ContextFactory : public IContextFactory {
public:
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
@@ -138,7 +163,7 @@
}
};
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
@@ -197,7 +222,7 @@
canvasContext->destroy();
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 4d0595e..3ded540 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -42,7 +42,7 @@
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -54,7 +54,7 @@
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
@@ -62,7 +62,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
Paint greenPaint;
@@ -76,7 +76,7 @@
renderNodes.push_back(halfGreenNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -89,7 +89,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -100,7 +100,7 @@
renderNodes.push_back(redNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -111,12 +111,12 @@
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
+RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
- auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
redNode->setLayerSurface(surfaceLayer1);
@@ -127,7 +127,7 @@
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
});
- auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
blueNode->setLayerSurface(surfaceLayer2);
@@ -154,7 +154,7 @@
blueNode->setLayerSurface(sk_sp<SkSurface>());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
+RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
ScopedProperty<bool> prop(Properties::debugOverdraw, true);
auto whiteNode = TestUtils::createSkiaNode(
@@ -169,7 +169,7 @@
// empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw
android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
// Initialize the canvas to blue.
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
@@ -227,7 +227,7 @@
};
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
+RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) {
class DeferTestCanvas : public SkCanvas {
public:
DeferTestCanvas() : SkCanvas(800, 600) {}
@@ -297,7 +297,7 @@
EXPECT_EQ(4, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 200;
class ClippedTestCanvas : public SkCanvas {
@@ -330,7 +330,7 @@
}
// Test renderFrame with a dirty clip and a pre-transform matrix.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 100;
static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
@@ -366,7 +366,7 @@
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
+RENDERTHREAD_TEST(SkiaPipeline, clip_replace) {
static const int CANVAS_WIDTH = 50;
static const int CANVAS_HEIGHT = 50;
class ClipReplaceTestCanvas : public SkCanvas {
@@ -396,7 +396,7 @@
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
+RENDERTHREAD_TEST(SkiaPipeline, context_lost) {
test::TestContext context;
auto surface = context.surface();
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
@@ -410,7 +410,7 @@
EXPECT_TRUE(pipeline->isSurfaceReady());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
+RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) {
// create a pipeline and add a picture callback
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
int callbackCount = 0;
@@ -428,7 +428,7 @@
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 499afa0..c71c4d2 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -29,6 +29,7 @@
#include "hwui/MinikinSkia.h"
#include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
using namespace android;
@@ -56,7 +57,7 @@
sk_sp<SkData> skData =
SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 0000000..c70a304
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+ void* p = const_cast<void*>(ptr);
+ size_t len = reinterpret_cast<size_t>(context);
+ munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+ int fd = open(fileName, O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+ void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ sk_sp<SkData> skData =
+ SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+ sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+ LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+ std::shared_ptr<minikin::MinikinFont> font =
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+ std::vector<minikin::FontVariation>());
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
+ fonts.push_back(minikin::Font::Builder(font).build());
+ return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+ return Typeface::createFromFamilies(
+ std::vector<std::shared_ptr<minikin::FontFamily>>(
+ {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+ return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+ 0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+ // Create canvas
+ SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+ sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ SkiaCanvas canvas(skBitmap);
+
+ // Create minikin::Layout
+ std::unique_ptr<Typeface> typeface(makeTypeface());
+ minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+ DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, paint, f);
+ return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // the text is "abc"
+ DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+ EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is あいう in Japanese
+ DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is aいc. The only middle of the character is Japanese.
+ DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+ // We use the bottom, thicker line as underline. Here, use Noto's one.
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+} // namespace
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index e1fb8b7..5e8f13d 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -26,9 +26,15 @@
using namespace android;
using namespace android::uirenderer;
+#define ASSUME_GLES() \
+ if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \
+ GTEST_SKIP() << "Not in GLES, skipping test"
+
TEST(WebViewFunctor, createDestroyGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
WebViewFunctor_release(functor);
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
@@ -41,8 +47,10 @@
}
TEST(WebViewFunctor, createSyncHandleGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -82,8 +90,10 @@
}
TEST(WebViewFunctor, createSyncDrawGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -108,9 +118,11 @@
EXPECT_EQ(1, counts.destroyed);
}
-TEST(WebViewFunctor, contextDestroyed) {
+TEST(WebViewFunctor, contextDestroyedGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
index 10c874e..76cbc8a 100644
--- a/libs/hwui/tests/unit/main.cpp
+++ b/libs/hwui/tests/unit/main.cpp
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include <getopt.h>
+#include <signal.h>
#include "Properties.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
#include "hwui/Typeface.h"
#include "tests/common/LeakChecker.h"
-#include <signal.h>
-
using namespace std;
using namespace android;
using namespace android::uirenderer;
@@ -45,6 +45,57 @@
raise(sig);
}
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+ Reserved = 255,
+ Renderer,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+ {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}};
+
+static RenderPipelineType parseRenderer(const char* renderer) {
+ // Anything that's not skiavk is skiagl
+ if (!strcmp(renderer, "skiavk")) {
+ return RenderPipelineType::SkiaVulkan;
+ }
+ return RenderPipelineType::SkiaGL;
+}
+
+struct Options {
+ RenderPipelineType renderer = RenderPipelineType::SkiaGL;
+};
+
+Options parseOptions(int argc, char* argv[]) {
+ int c;
+ opterr = 0;
+ Options opts;
+
+ while (true) {
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index);
+
+ if (c == -1) break;
+
+ switch (c) {
+ case 0:
+ // Option set a flag, don't need to do anything
+ // (although none of the current LONG_OPTIONS do this...)
+ break;
+
+ case LongOpts::Renderer:
+ opts.renderer = parseRenderer(optarg);
+ break;
+ }
+ }
+ return opts;
+}
+
class TypefaceEnvironment : public testing::Environment {
public:
virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); }
@@ -64,8 +115,9 @@
// Avoid talking to SF
Properties::isolatedProcess = true;
- // Default to GLES (Vulkan-aware tests will override this)
- Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+
+ auto opts = parseOptions(argc, argv);
+ Properties::overrideRenderPipelineType(opts.renderer);
// Run the tests
testing::InitGoogleTest(&argc, argv);
diff --git a/libs/hwui/utils/ForceDark.h b/libs/hwui/utils/ForceDark.h
new file mode 100644
index 0000000..28538c4b
--- /dev/null
+++ b/libs/hwui/utils/ForceDark.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef FORCEDARKUTILS_H
+#define FORCEDARKUTILS_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * The type of force dark set on the renderer, if any.
+ *
+ * This should stay in sync with the java @IntDef in
+ * frameworks/base/graphics/java/android/graphics/ForceDarkType.java
+ */
+enum class ForceDarkType : __uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // FORCEDARKUTILS_H
\ No newline at end of file
diff --git a/libs/hwui/utils/TypefaceUtils.cpp b/libs/hwui/utils/TypefaceUtils.cpp
new file mode 100644
index 0000000..a30b925
--- /dev/null
+++ b/libs/hwui/utils/TypefaceUtils.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <utils/TypefaceUtils.h>
+
+#include "include/ports/SkFontMgr_empty.h"
+
+namespace android {
+
+sk_sp<SkFontMgr> FreeTypeFontMgr() {
+ static sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty();
+ return mgr;
+}
+
+} // namespace android
diff --git a/libs/hwui/utils/TypefaceUtils.h b/libs/hwui/utils/TypefaceUtils.h
new file mode 100644
index 0000000..c0adeae
--- /dev/null
+++ b/libs/hwui/utils/TypefaceUtils.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+
+namespace android {
+
+// Return an SkFontMgr which is capable of turning bytes into a SkTypeface using Freetype.
+// There are no other fonts inside this SkFontMgr (e.g. no system fonts).
+sk_sp<SkFontMgr> FreeTypeFontMgr();
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 6c0fd5f..5ce990f 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -23,6 +23,12 @@
cc_library_shared {
name: "libinputservice",
+ defaults: [
+ // Build using the same flags and configurations as inputflinger.
+ "inputflinger_defaults",
+ ],
+ host_supported: false,
+
srcs: [
"PointerController.cpp",
"PointerControllerContext.cpp",
@@ -50,12 +56,4 @@
],
include_dirs: ["frameworks/native/services"],
-
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- "-Wthread-safety",
- ],
-
}
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index c3ad767..6a46544 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -47,7 +47,7 @@
mLocked.pointerX = 0;
mLocked.pointerY = 0;
mLocked.pointerAlpha = 0.0f; // pointer is initially faded
- mLocked.pointerSprite = mContext.getSpriteController()->createSprite();
+ mLocked.pointerSprite = mContext.getSpriteController().createSprite();
mLocked.updatePointerIcon = false;
mLocked.requestedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
@@ -325,8 +325,8 @@
}
if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
- sp<SpriteController> spriteController = mContext.getSpriteController();
- spriteController->openTransaction();
+ auto& spriteController = mContext.getSpriteController();
+ spriteController.openTransaction();
int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
mLocked.animationFrameIndex += incr;
@@ -336,7 +336,7 @@
}
mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
- spriteController->closeTransaction();
+ spriteController.closeTransaction();
}
// Keep animating.
return true;
@@ -346,8 +346,8 @@
if (!mLocked.viewport.isValid()) {
return;
}
- sp<SpriteController> spriteController = mContext.getSpriteController();
- spriteController->openTransaction();
+ auto& spriteController = mContext.getSpriteController();
+ spriteController.openTransaction();
mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
@@ -392,7 +392,7 @@
mLocked.updatePointerIcon = false;
}
- spriteController->closeTransaction();
+ spriteController.closeTransaction();
}
void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index e21d6fb..bba9c97 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -24,6 +24,7 @@
#include <SkColor.h>
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
+#include <com_android_input_flags.h>
#include <ftl/enum.h>
#include <mutex>
@@ -34,6 +35,8 @@
#define INDENT2 " "
#define INDENT3 " "
+namespace input_flags = com::android::input::flags;
+
namespace android {
namespace {
@@ -63,10 +66,28 @@
std::shared_ptr<PointerController> PointerController::create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController) {
+ SpriteController& spriteController, bool enabled, ControllerType type) {
// using 'new' to access non-public constructor
- std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
- new PointerController(policy, looper, spriteController));
+ std::shared_ptr<PointerController> controller;
+ switch (type) {
+ case ControllerType::MOUSE:
+ controller = std::shared_ptr<PointerController>(
+ new MousePointerController(policy, looper, spriteController, enabled));
+ break;
+ case ControllerType::TOUCH:
+ controller = std::shared_ptr<PointerController>(
+ new TouchPointerController(policy, looper, spriteController, enabled));
+ break;
+ case ControllerType::STYLUS:
+ controller = std::shared_ptr<PointerController>(
+ new StylusPointerController(policy, looper, spriteController, enabled));
+ break;
+ case ControllerType::LEGACY:
+ default:
+ controller = std::shared_ptr<PointerController>(
+ new PointerController(policy, looper, spriteController, enabled));
+ break;
+ }
/*
* Now we need to hook up the constructed PointerController object to its callbacks.
@@ -85,10 +106,10 @@
}
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper,
- const sp<SpriteController>& spriteController)
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled)
: PointerController(
- policy, looper, spriteController,
+ policy, looper, spriteController, enabled,
[](const sp<android::gui::WindowInfosListener>& listener) {
SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
},
@@ -97,13 +118,13 @@
}) {}
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper,
- const sp<SpriteController>& spriteController,
- WindowListenerConsumer registerListener,
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled, WindowListenerConsumer registerListener,
WindowListenerConsumer unregisterListener)
- : mContext(policy, looper, spriteController, *this),
+ : mEnabled(enabled),
+ mContext(policy, looper, spriteController, *this),
mCursorController(mContext),
- mDisplayInfoListener(new DisplayInfoListener(this)),
+ mDisplayInfoListener(sp<DisplayInfoListener>::make(this)),
mUnregisterWindowInfosListener(std::move(unregisterListener)) {
std::scoped_lock lock(getLock());
mLocked.presentation = Presentation::SPOT;
@@ -121,10 +142,14 @@
}
std::optional<FloatRect> PointerController::getBounds() const {
+ if (!mEnabled) return {};
+
return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
+ if (!mEnabled) return;
+
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
{
@@ -136,6 +161,8 @@
}
void PointerController::setPosition(float x, float y) {
+ if (!mEnabled) return;
+
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
{
@@ -147,6 +174,10 @@
}
FloatPoint PointerController::getPosition() const {
+ if (!mEnabled) {
+ return FloatPoint{0, 0};
+ }
+
const int32_t displayId = mCursorController.getDisplayId();
const auto p = mCursorController.getPosition();
{
@@ -157,20 +188,28 @@
}
int32_t PointerController::getDisplayId() const {
+ if (!mEnabled) return ADISPLAY_ID_NONE;
+
return mCursorController.getDisplayId();
}
void PointerController::fade(Transition transition) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.fade(transition);
}
void PointerController::unfade(Transition transition) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.unfade(transition);
}
void PointerController::setPresentation(Presentation presentation) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
if (mLocked.presentation == presentation) {
@@ -179,6 +218,15 @@
mLocked.presentation = presentation;
+ if (input_flags::enable_pointer_choreographer()) {
+ // When pointer choreographer is enabled, the presentation mode is only set once when the
+ // PointerController is constructed, before the display viewport is provided.
+ // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
+ // is permanently enabled. The presentation can be set in the constructor.
+ mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+ return;
+ }
+
if (!mCursorController.isViewportValid()) {
return;
}
@@ -195,6 +243,8 @@
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, int32_t displayId) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -218,6 +268,8 @@
}
void PointerController::clearSpots() {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
clearSpotsLocked();
}
@@ -279,11 +331,15 @@
}
void PointerController::updatePointerIcon(PointerIconStyle iconId) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.updatePointerIcon(iconId);
}
void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.setCustomPointerIcon(icon);
}
@@ -292,7 +348,7 @@
fade(Transition::GRADUAL);
}
-void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
+void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) {
std::unordered_set<int32_t> displayIdSet;
for (const DisplayViewport& viewport : viewports) {
displayIdSet.insert(viewport.displayId);
@@ -327,8 +383,12 @@
return it != di.end() ? it->transform : kIdentityTransform;
}
-void PointerController::dump(std::string& dump) {
- dump += INDENT "PointerController:\n";
+std::string PointerController::dump() {
+ if (!mEnabled) {
+ return INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n";
+ }
+
+ std::string dump = INDENT "PointerController:\n";
std::scoped_lock lock(getLock());
dump += StringPrintf(INDENT2 "Presentation: %s\n",
ftl::enum_string(mLocked.presentation).c_str());
@@ -341,6 +401,46 @@
for (const auto& [_, spotController] : mLocked.spotControllers) {
spotController.dump(dump, INDENT3);
}
+ return dump;
+}
+
+// --- MousePointerController ---
+
+MousePointerController::MousePointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper,
+ SpriteController& spriteController, bool enabled)
+ : PointerController(policy, looper, spriteController, enabled) {
+ PointerController::setPresentation(Presentation::POINTER);
+}
+
+MousePointerController::~MousePointerController() {
+ MousePointerController::fade(Transition::IMMEDIATE);
+}
+
+// --- TouchPointerController ---
+
+TouchPointerController::TouchPointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper,
+ SpriteController& spriteController, bool enabled)
+ : PointerController(policy, looper, spriteController, enabled) {
+ PointerController::setPresentation(Presentation::SPOT);
+}
+
+TouchPointerController::~TouchPointerController() {
+ TouchPointerController::clearSpots();
+}
+
+// --- StylusPointerController ---
+
+StylusPointerController::StylusPointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper,
+ SpriteController& spriteController, bool enabled)
+ : PointerController(policy, looper, spriteController, enabled) {
+ PointerController::setPresentation(Presentation::STYLUS_HOVER);
+}
+
+StylusPointerController::~StylusPointerController() {
+ StylusPointerController::fade(Transition::IMMEDIATE);
}
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 62ee743..a8b9633 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,8 @@
public:
static std::shared_ptr<PointerController> create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController);
+ SpriteController& spriteController, bool enabled,
+ ControllerType type = ControllerType::LEGACY);
~PointerController() override;
@@ -64,18 +65,18 @@
void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, int32_t displayId) override;
void clearSpots() override;
+ void updatePointerIcon(PointerIconStyle iconId) override;
+ void setCustomPointerIcon(const SpriteIcon& icon) override;
- void updatePointerIcon(PointerIconStyle iconId);
- void setCustomPointerIcon(const SpriteIcon& icon);
- void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+ virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
void reloadPointerResources();
- void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
+ void onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports);
void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos)
REQUIRES(getLock());
- void dump(std::string& dump);
+ std::string dump() override;
protected:
using WindowListenerConsumer =
@@ -83,14 +84,14 @@
// Constructor used to test WindowInfosListener registration.
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController,
+ SpriteController& spriteController, bool enabled,
WindowListenerConsumer registerListener,
WindowListenerConsumer unregisterListener);
-private:
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController);
+ SpriteController& spriteController, bool enabled);
+private:
friend PointerControllerContext::LooperCallback;
friend PointerControllerContext::MessageHandler;
@@ -100,6 +101,8 @@
// we use the DisplayInfoListener's lock in PointerController.
std::mutex& getLock() const;
+ const bool mEnabled;
+
PointerControllerContext mContext;
MouseCursorController mCursorController;
@@ -133,6 +136,92 @@
void clearSpotsLocked() REQUIRES(getLock());
};
+class MousePointerController : public PointerController {
+public:
+ /** A version of PointerController that controls one mouse pointer. */
+ MousePointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled);
+
+ ~MousePointerController() override;
+
+ void setPresentation(Presentation) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void clearSpots() override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+};
+
+class TouchPointerController : public PointerController {
+public:
+ /** A version of PointerController that controls touch spots. */
+ TouchPointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled);
+
+ ~TouchPointerController() override;
+
+ std::optional<FloatRect> getBounds() const override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void move(float, float) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void setPosition(float, float) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ FloatPoint getPosition() const override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ int32_t getDisplayId() const override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void fade(Transition) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void unfade(Transition) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void setDisplayViewport(const DisplayViewport&) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void setPresentation(Presentation) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void updatePointerIcon(PointerIconStyle) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void setCustomPointerIcon(const SpriteIcon&) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ // fade() should not be called by inactivity timeout. Do nothing.
+ void setInactivityTimeout(InactivityTimeout) override {}
+};
+
+class StylusPointerController : public PointerController {
+public:
+ /** A version of PointerController that controls one stylus pointer. */
+ StylusPointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled);
+
+ ~StylusPointerController() override;
+
+ void setPresentation(Presentation) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+ void clearSpots() override {
+ LOG_ALWAYS_FATAL("Should not be called");
+ }
+};
+
} // namespace android
#endif // _UI_POINTER_CONTROLLER_H
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index f30e8d8..15c3517 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -32,12 +32,12 @@
PointerControllerContext::PointerControllerContext(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController, PointerController& controller)
+ SpriteController& spriteController, PointerController& controller)
: mPolicy(policy),
mLooper(looper),
mSpriteController(spriteController),
- mHandler(new MessageHandler()),
- mCallback(new LooperCallback()),
+ mHandler(sp<MessageHandler>::make()),
+ mCallback(sp<LooperCallback>::make()),
mController(controller),
mAnimator(*this) {
std::scoped_lock lock(mLock);
@@ -93,7 +93,7 @@
return mPolicy;
}
-sp<SpriteController> PointerControllerContext::getSpriteController() {
+SpriteController& PointerControllerContext::getSpriteController() {
return mSpriteController;
}
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index f6f5d3b..98c3988 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -92,7 +92,7 @@
class PointerControllerContext {
public:
PointerControllerContext(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper, const sp<SpriteController>& spriteController,
+ const sp<Looper>& looper, SpriteController& spriteController,
PointerController& controller);
~PointerControllerContext();
@@ -109,7 +109,7 @@
void setCallbackController(std::shared_ptr<PointerController> controller);
sp<PointerControllerPolicyInterface> getPolicy();
- sp<SpriteController> getSpriteController();
+ SpriteController& getSpriteController();
void handleDisplayEvents();
@@ -163,7 +163,7 @@
sp<PointerControllerPolicyInterface> mPolicy;
sp<Looper> mLooper;
- sp<SpriteController> mSpriteController;
+ SpriteController& mSpriteController;
sp<MessageHandler> mHandler;
sp<LooperCallback> mCallback;
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 130b204..6dc45a6 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -31,12 +31,19 @@
ParentSurfaceProvider parentSurfaceProvider)
: mLooper(looper),
mOverlayLayer(overlayLayer),
+ mHandler(sp<Handler>::make()),
mParentSurfaceProvider(std::move(parentSurfaceProvider)) {
- mHandler = new WeakMessageHandler(this);
mLocked.transactionNestingCount = 0;
mLocked.deferredSpriteUpdate = false;
}
+void SpriteController::setHandlerController(
+ const std::shared_ptr<android::SpriteController>& controller) {
+ // Initialize the weak message handler outside the constructor, because we cannot get a shared
+ // pointer to self in the constructor.
+ mHandler->spriteController = controller;
+}
+
SpriteController::~SpriteController() {
mLooper->removeMessages(mHandler);
@@ -47,7 +54,7 @@
}
sp<Sprite> SpriteController::createSprite() {
- return new SpriteImpl(this);
+ return sp<SpriteImpl>::make(*this);
}
void SpriteController::openTransaction() {
@@ -65,7 +72,7 @@
mLocked.transactionNestingCount -= 1;
if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
mLocked.deferredSpriteUpdate = false;
- mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ mLooper->sendMessage(mHandler, Message(Handler::MSG_UPDATE_SPRITES));
}
}
@@ -76,7 +83,7 @@
if (mLocked.transactionNestingCount != 0) {
mLocked.deferredSpriteUpdate = true;
} else {
- mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ mLooper->sendMessage(mHandler, Message(Handler::MSG_UPDATE_SPRITES));
}
}
}
@@ -85,18 +92,7 @@
bool wasEmpty = mLocked.disposedSurfaces.empty();
mLocked.disposedSurfaces.push_back(surfaceControl);
if (wasEmpty) {
- mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
- }
-}
-
-void SpriteController::handleMessage(const Message& message) {
- switch (message.what) {
- case MSG_UPDATE_SPRITES:
- doUpdateSprites();
- break;
- case MSG_DISPOSE_SURFACES:
- doDisposeSurfaces();
- break;
+ mLooper->sendMessage(mHandler, Message(Handler::MSG_DISPOSE_SURFACES));
}
}
@@ -327,7 +323,7 @@
void SpriteController::ensureSurfaceComposerClient() {
if (mSurfaceComposerClient == NULL) {
- mSurfaceComposerClient = new SurfaceComposerClient();
+ mSurfaceComposerClient = sp<SurfaceComposerClient>::make();
}
}
@@ -353,25 +349,41 @@
return surfaceControl;
}
-// --- SpriteController::SpriteImpl ---
+// --- SpriteController::Handler ---
-SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
- mController(controller) {
+void SpriteController::Handler::handleMessage(const android::Message& message) {
+ auto controller = spriteController.lock();
+ if (!controller) {
+ return;
+ }
+
+ switch (message.what) {
+ case MSG_UPDATE_SPRITES:
+ controller->doUpdateSprites();
+ break;
+ case MSG_DISPOSE_SURFACES:
+ controller->doDisposeSurfaces();
+ break;
+ }
}
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(SpriteController& controller) : mController(controller) {}
+
SpriteController::SpriteImpl::~SpriteImpl() {
- AutoMutex _m(mController->mLock);
+ AutoMutex _m(mController.mLock);
// Let the controller take care of deleting the last reference to sprite
// surfaces so that we do not block the caller on an IPC here.
if (mLocked.state.surfaceControl != NULL) {
- mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mController.disposeSurfaceLocked(mLocked.state.surfaceControl);
mLocked.state.surfaceControl.clear();
}
}
void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
uint32_t dirty;
if (icon.isValid()) {
@@ -401,7 +413,7 @@
}
void SpriteController::SpriteImpl::setVisible(bool visible) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.visible != visible) {
mLocked.state.visible = visible;
@@ -410,7 +422,7 @@
}
void SpriteController::SpriteImpl::setPosition(float x, float y) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
mLocked.state.positionX = x;
@@ -420,7 +432,7 @@
}
void SpriteController::SpriteImpl::setLayer(int32_t layer) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.layer != layer) {
mLocked.state.layer = layer;
@@ -429,7 +441,7 @@
}
void SpriteController::SpriteImpl::setAlpha(float alpha) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.alpha != alpha) {
mLocked.state.alpha = alpha;
@@ -439,7 +451,7 @@
void SpriteController::SpriteImpl::setTransformationMatrix(
const SpriteTransformationMatrix& matrix) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.transformationMatrix != matrix) {
mLocked.state.transformationMatrix = matrix;
@@ -448,7 +460,7 @@
}
void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.displayId != displayId) {
mLocked.state.displayId = displayId;
@@ -461,7 +473,7 @@
mLocked.state.dirty |= dirty;
if (!wasDirty) {
- mController->invalidateSpriteLocked(this);
+ mController.invalidateSpriteLocked(sp<SpriteImpl>::fromExisting(this));
}
}
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 1f113c0..04ecb38 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -109,15 +109,19 @@
*
* Clients are responsible for animating sprites by periodically updating their properties.
*/
-class SpriteController : public MessageHandler {
-protected:
- virtual ~SpriteController();
-
+class SpriteController {
public:
using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
+ SpriteController(const SpriteController&) = delete;
+ SpriteController& operator=(const SpriteController&) = delete;
+ virtual ~SpriteController();
- /* Creates a new sprite, initially invisible. */
+ /* Initialize the callback for the message handler. */
+ void setHandlerController(const std::shared_ptr<SpriteController>& controller);
+
+ /* Creates a new sprite, initially invisible. The lifecycle of the sprite must not extend beyond
+ * the lifecycle of this SpriteController. */
virtual sp<Sprite> createSprite();
/* Opens or closes a transaction to perform a batch of sprite updates as part of
@@ -129,9 +133,12 @@
virtual void closeTransaction();
private:
- enum {
- MSG_UPDATE_SPRITES,
- MSG_DISPOSE_SURFACES,
+ class Handler : public virtual android::MessageHandler {
+ public:
+ enum { MSG_UPDATE_SPRITES, MSG_DISPOSE_SURFACES };
+
+ void handleMessage(const Message& message) override;
+ std::weak_ptr<SpriteController> spriteController;
};
enum {
@@ -192,7 +199,7 @@
virtual ~SpriteImpl();
public:
- explicit SpriteImpl(const sp<SpriteController> controller);
+ explicit SpriteImpl(SpriteController& controller);
virtual void setIcon(const SpriteIcon& icon);
virtual void setVisible(bool visible);
@@ -220,7 +227,7 @@
}
private:
- sp<SpriteController> mController;
+ SpriteController& mController;
struct Locked {
SpriteState state;
@@ -245,7 +252,7 @@
sp<Looper> mLooper;
const int32_t mOverlayLayer;
- sp<WeakMessageHandler> mHandler;
+ sp<Handler> mHandler;
ParentSurfaceProvider mParentSurfaceProvider;
sp<SurfaceComposerClient> mSurfaceComposerClient;
@@ -260,7 +267,6 @@
void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
- void handleMessage(const Message& message);
void doUpdateSprites();
void doDisposeSurfaces();
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index d9fe599..b8de919 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -39,15 +39,15 @@
// --- Spot ---
-void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
+void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
int32_t displayId) {
sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
sprite->setAlpha(alpha);
sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
- sprite->setPosition(x, y);
+ sprite->setPosition(newX, newY);
sprite->setDisplayId(displayId);
- this->x = x;
- this->y = y;
+ x = newX;
+ y = newY;
if (icon != mLastIcon) {
mLastIcon = icon;
@@ -98,8 +98,8 @@
#endif
std::scoped_lock lock(mLock);
- sp<SpriteController> spriteController = mContext.getSpriteController();
- spriteController->openTransaction();
+ auto& spriteController = mContext.getSpriteController();
+ spriteController.openTransaction();
// Add or move spots for fingers that are down.
for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
@@ -125,7 +125,7 @@
}
}
- spriteController->closeTransaction();
+ spriteController.closeTransaction();
}
void TouchSpotController::clearSpots() {
@@ -167,7 +167,7 @@
sprite = mLocked.recycledSprites.back();
mLocked.recycledSprites.pop_back();
} else {
- sprite = mContext.getSpriteController()->createSprite();
+ sprite = mContext.getSpriteController().createSprite();
}
// Return the new spot.
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 4eabfb2..69718a6 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -26,14 +26,32 @@
srcs: [
"PointerController_test.cpp",
],
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ },
shared_libs: [
"libandroid_runtime",
+ "libbase",
+ "libinput",
"libinputservice",
"libhwui",
"libgui",
"libutils",
],
static_libs: [
+ "libflagtest",
"libgmock",
"libgtest",
],
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 8574751..adfa91e 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <input/PointerController.h>
@@ -28,6 +30,8 @@
namespace android {
+namespace input_flags = com::android::input::flags;
+
enum TestCursorType {
CURSOR_TYPE_DEFAULT = 0,
CURSOR_TYPE_HOVER,
@@ -148,6 +152,25 @@
latestPointerDisplayId = displayId;
}
+class TestPointerController : public PointerController {
+public:
+ TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
+ sp<PointerControllerPolicyInterface> policy, const sp<Looper>& looper,
+ SpriteController& spriteController)
+ : PointerController(
+ policy, looper, spriteController,
+ /*enabled=*/true,
+ [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Register listener
+ registeredListener = listener;
+ },
+ [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Unregister listener
+ if (registeredListener == listener) registeredListener = nullptr;
+ }) {}
+ ~TestPointerController() override {}
+};
+
class PointerControllerTest : public Test {
protected:
PointerControllerTest();
@@ -157,8 +180,9 @@
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
- sp<MockSpriteController> mSpriteController;
+ std::unique_ptr<MockSpriteController> mSpriteController;
std::shared_ptr<PointerController> mPointerController;
+ sp<android::gui::WindowInfosListener> mRegisteredListener;
private:
void loopThread();
@@ -175,17 +199,18 @@
PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
-
- mSpriteController = new NiceMock<MockSpriteController>(mLooper);
+ mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper));
mPolicy = new MockPointerControllerPolicyInterface();
EXPECT_CALL(*mSpriteController, createSprite())
.WillOnce(Return(mPointerSprite));
- mPointerController = PointerController::create(mPolicy, mLooper, mSpriteController);
+ mPointerController = std::make_unique<TestPointerController>(mRegisteredListener, mPolicy,
+ mLooper, *mSpriteController);
}
PointerControllerTest::~PointerControllerTest() {
+ mPointerController.reset();
mRunning.store(false, std::memory_order_relaxed);
mThread.join();
}
@@ -240,7 +265,20 @@
mPointerController->reloadPointerResources();
}
-TEST_F(PointerControllerTest, updatePointerIcon) {
+TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+ // Setting the presentation mode before a display viewport is set will not load any resources.
+ mPointerController->setPresentation(PointerController::Presentation::POINTER);
+ ASSERT_TRUE(mPolicy->noResourcesAreLoaded());
+
+ // When the display is set, then the resources are loaded.
+ ensureDisplayViewportIsSet();
+ ASSERT_TRUE(mPolicy->allResourcesAreLoaded());
+}
+
+TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags,
+ enable_pointer_choreographer))) {
ensureDisplayViewportIsSet();
mPointerController->setPresentation(PointerController::Presentation::POINTER);
mPointerController->unfade(PointerController::Transition::IMMEDIATE);
@@ -256,6 +294,24 @@
mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
}
+TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+ // When PointerChoreographer is enabled, the presentation mode is set before the viewport.
+ mPointerController->setPresentation(PointerController::Presentation::POINTER);
+ ensureDisplayViewportIsSet();
+ mPointerController->unfade(PointerController::Transition::IMMEDIATE);
+
+ int32_t type = CURSOR_TYPE_ADDITIONAL;
+ std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
+ EXPECT_CALL(*mPointerSprite, setVisible(true));
+ EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+ EXPECT_CALL(*mPointerSprite,
+ setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
+}
+
TEST_F(PointerControllerTest, setCustomPointerIcon) {
ensureDisplayViewportIsSet();
mPointerController->unfade(PointerController::Transition::IMMEDIATE);
@@ -316,29 +372,16 @@
class PointerControllerWindowInfoListenerTest : public Test {};
-class TestPointerController : public PointerController {
-public:
- TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
- const sp<Looper>& looper)
- : PointerController(
- new MockPointerControllerPolicyInterface(), looper,
- new NiceMock<MockSpriteController>(looper),
- [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
- // Register listener
- registeredListener = listener;
- },
- [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) {
- // Unregister listener
- if (registeredListener == listener) registeredListener = nullptr;
- }) {}
-};
-
TEST_F(PointerControllerWindowInfoListenerTest,
doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) {
+ sp<Looper> looper = new Looper(false);
+ auto spriteController = NiceMock<MockSpriteController>(looper);
sp<android::gui::WindowInfosListener> registeredListener;
sp<android::gui::WindowInfosListener> localListenerCopy;
+ sp<MockPointerControllerPolicyInterface> policy = new MockPointerControllerPolicyInterface();
{
- TestPointerController pointerController(registeredListener, new Looper(false));
+ TestPointerController pointerController(registeredListener, policy, looper,
+ spriteController);
ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
localListenerCopy = registeredListener;
}