Merge "Implement low light dream library." into tm-qpr-dev
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a70a1a8..51efdba 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -534,9 +534,8 @@
// A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
// used without security checks
public IBinder shareableActivityToken;
- // The token of the initial TaskFragment that embedded this activity. Do not rely on it
- // after creation because the activity could be reparented.
- @Nullable public IBinder mInitialTaskFragmentToken;
+ // The token of the TaskFragment that embedded this activity.
+ @Nullable public IBinder mTaskFragmentToken;
int ident;
@UnsupportedAppUsage
Intent intent;
@@ -620,7 +619,7 @@
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
- IBinder initialTaskFragmentToken) {
+ IBinder taskFragmentToken) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
@@ -641,7 +640,7 @@
compatInfo);
mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble;
- mInitialTaskFragmentToken = initialTaskFragmentToken;
+ mTaskFragmentToken = taskFragmentToken;
init();
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0bbe8c4..4be5e14 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1119,6 +1119,16 @@
public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L;
/**
+ * Overrides the min aspect ratio restriction in portrait fullscreen in order to use all
+ * available screen space.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN = 218959984L;
+
+ /**
* Compares activity window layout min width/height with require space for multi window to
* determine if it can be put into multi window mode.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 65bec0e..44cfe1a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -264,11 +264,6 @@
void stopTracing();
/**
- * Handles a logging command from the WM shell command.
- */
- void handleWindowManagerLoggingCommand(in String[] args, in ParcelFileDescriptor outFd);
-
- /**
* If true, suppresses the ambient display from showing. If false, re-enables the ambient
* display.
*/
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ae55e36..483efd86c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2060,6 +2060,10 @@
<!-- The default volume for the ring stream -->
<integer name="config_audio_ring_vol_default">5</integer>
+ <!-- The default value for whether head tracking for
+ spatial audio is enabled for a newly connected audio device -->
+ <bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
+
<!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
on grouped devices. -->
<bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 123770e..71919ff 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -278,6 +278,7 @@
<java-symbol type="integer" name="config_audio_notif_vol_steps" />
<java-symbol type="integer" name="config_audio_ring_vol_default" />
<java-symbol type="integer" name="config_audio_ring_vol_steps" />
+ <java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
<java-symbol type="integer" name="config_chooser_max_targets_per_row" />
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 5727b91..724a50f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -583,7 +583,7 @@
}
if (!isOnReparent && getContainerWithActivity(activity) == null
- && getInitialTaskFragmentToken(activity) != null) {
+ && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
// We can't find the new launched activity in any recorded container, but it is
// currently placed in an embedded TaskFragment. This can happen in two cases:
// 1. the activity is embedded in another app.
@@ -866,11 +866,12 @@
}
@VisibleForTesting
+ @GuardedBy("mLock")
void onActivityDestroyed(@NonNull Activity activity) {
// Remove any pending appeared activity, as the server won't send finished activity to the
// organizer.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).cleanupPendingAppearedActivity(activity);
+ mTaskContainers.valueAt(i).onActivityDestroyed(activity);
}
// We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed.
@@ -1605,15 +1606,16 @@
}
/**
- * Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it
- * after creation because the activity could be reparented.
+ * Gets the token of the TaskFragment that embedded this activity. It is available as soon as
+ * the activity is created and attached, so it can be used during {@link #onActivityCreated}
+ * before the server notifies the organizer to avoid racing condition.
*/
@VisibleForTesting
@Nullable
- IBinder getInitialTaskFragmentToken(@NonNull Activity activity) {
+ IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
.getActivityClient(activity.getActivityToken());
- return record != null ? record.mInitialTaskFragmentToken : null;
+ return record != null ? record.mTaskFragmentToken : null;
}
/**
@@ -1691,7 +1693,8 @@
@Nullable Bundle savedInstanceState) {
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
- final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
+ final IBinder initialTaskFragmentToken =
+ getTaskFragmentTokenFromActivityClientRecord(activity);
// If the activity is not embedded, then it will not have an initial task fragment
// token so no further action is needed.
if (initialTaskFragmentToken == null) {
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 45645b2..b563677 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -137,6 +137,13 @@
return mContainers.isEmpty() && mFinishedContainer.isEmpty();
}
+ /** Called when the activity is destroyed. */
+ void onActivityDestroyed(@NonNull Activity activity) {
+ for (TaskFragmentContainer container : mContainers) {
+ container.onActivityDestroyed(activity);
+ }
+ }
+
/** Removes the pending appeared activity from all TaskFragments in this Task. */
void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
for (TaskFragmentContainer container : mContainers) {
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 2843b14..626e0d9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity;
+import android.app.ActivityThread;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.graphics.Rect;
@@ -190,6 +191,19 @@
// Remove the pending activity from other TaskFragments.
mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
mPendingAppearedActivities.add(pendingAppearedActivity);
+ updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity);
+ }
+
+ /**
+ * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
+ * activity. This makes sure the token is up-to-date if the activity is relaunched later.
+ */
+ private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) {
+ final ActivityThread.ActivityClientRecord record = ActivityThread
+ .currentActivityThread().getActivityClient(activity.getActivityToken());
+ if (record != null) {
+ record.mTaskFragmentToken = mToken;
+ }
}
void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
@@ -197,8 +211,29 @@
}
void clearPendingAppearedActivities() {
+ final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
+ // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
+ // current TaskFragment.
mPendingAppearedActivities.clear();
mPendingAppearedIntent = null;
+
+ // For removed pending activities, we need to update the them to their previous containers.
+ for (Activity activity : cleanupActivities) {
+ final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
+ activity);
+ if (curContainer != null) {
+ curContainer.updateActivityClientRecordTaskFragmentToken(activity);
+ }
+ }
+ }
+
+ /** Called when the activity is destroyed. */
+ void onActivityDestroyed(@NonNull Activity activity) {
+ removePendingAppearedActivity(activity);
+ if (mInfo != null) {
+ // Remove the activity now because there can be a delay before the server callback.
+ mInfo.getActivities().remove(activity.getActivityToken());
+ }
}
@Nullable
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 4dbbc04..58870a6 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
@@ -930,7 +930,8 @@
@Test
public void testResolveActivityToContainer_inUnknownTaskFragment() {
- doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
+ doReturn(new Binder()).when(mSplitController)
+ .getTaskFragmentTokenFromActivityClientRecord(mActivity);
// No need to handle when the new launched activity is in an unknown TaskFragment.
assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,
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 1bc81ee..082774e 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
@@ -316,6 +316,25 @@
assertEquals(activity, container.getBottomMostActivity());
}
+ @Test
+ public void testOnActivityDestroyed() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController);
+ container.addPendingAppearedActivity(mActivity);
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.hasActivity(mActivity.getActivityToken()));
+
+ taskContainer.onActivityDestroyed(mActivity);
+
+ // It should not contain the destroyed Activity.
+ assertFalse(container.hasActivity(mActivity.getActivityToken()));
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index d2dd8d6b..5696b8d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -81,6 +81,9 @@
<!-- The width and height of the background for custom action in PiP menu. -->
<dimen name="pip_custom_close_bg_size">32dp</dimen>
+ <!-- Extra padding between picture-in-picture windows and any registered keep clear areas. -->
+ <dimen name="pip_keep_clear_areas_padding">16dp</dimen>
+
<dimen name="dismiss_target_x_size">24dp</dimen>
<dimen name="floating_dismiss_bottom_margin">50dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
new file mode 100644
index 0000000..d276002
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import com.android.wm.shell.protolog.ShellProtoLogImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Controls the {@link ShellProtoLogImpl} in WMShell via adb shell commands.
+ *
+ * Use with {@code adb shell dumpsys activity service SystemUIService WMShell protolog ...}.
+ */
+public class ProtoLogController implements ShellCommandHandler.ShellCommandActionHandler {
+ private final ShellCommandHandler mShellCommandHandler;
+ private final ShellProtoLogImpl mShellProtoLog;
+
+ public ProtoLogController(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler) {
+ shellInit.addInitCallback(this::onInit, this);
+ mShellCommandHandler = shellCommandHandler;
+ mShellProtoLog = ShellProtoLogImpl.getSingleInstance();
+ }
+
+ void onInit() {
+ mShellCommandHandler.addCommandCallback("protolog", this, this);
+ }
+
+ @Override
+ public boolean onShellCommand(String[] args, PrintWriter pw) {
+ switch (args[0]) {
+ case "status": {
+ pw.println(mShellProtoLog.getStatus());
+ return true;
+ }
+ case "start": {
+ mShellProtoLog.startProtoLog(pw);
+ return true;
+ }
+ case "stop": {
+ mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
+ return true;
+ }
+ case "enable-text": {
+ String[] groups = Arrays.copyOfRange(args, 1, args.length);
+ int result = mShellProtoLog.startTextLogging(groups, pw);
+ if (result == 0) {
+ pw.println("Starting logging on groups: " + Arrays.toString(groups));
+ return true;
+ }
+ return false;
+ }
+ case "disable-text": {
+ String[] groups = Arrays.copyOfRange(args, 1, args.length);
+ int result = mShellProtoLog.stopTextLogging(groups, pw);
+ if (result == 0) {
+ pw.println("Stopping logging on groups: " + Arrays.toString(groups));
+ return true;
+ }
+ return false;
+ }
+ case "enable": {
+ String[] groups = Arrays.copyOfRange(args, 1, args.length);
+ return mShellProtoLog.startTextLogging(groups, pw) == 0;
+ }
+ case "disable": {
+ String[] groups = Arrays.copyOfRange(args, 1, args.length);
+ return mShellProtoLog.stopTextLogging(groups, pw) == 0;
+ }
+ default: {
+ pw.println("Invalid command: " + args[0]);
+ printShellCommandHelp(pw, "");
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public void printShellCommandHelp(PrintWriter pw, String prefix) {
+ pw.println(prefix + "status");
+ pw.println(prefix + " Get current ProtoLog status.");
+ pw.println(prefix + "start");
+ pw.println(prefix + " Start proto logging.");
+ pw.println(prefix + "stop");
+ pw.println(prefix + " Stop proto logging and flush to file.");
+ pw.println(prefix + "enable [group...]");
+ pw.println(prefix + " Enable proto logging for given groups.");
+ pw.println(prefix + "disable [group...]");
+ pw.println(prefix + " Disable proto logging for given groups.");
+ pw.println(prefix + "enable-text [group...]");
+ pw.println(prefix + " Enable logcat logging for given groups.");
+ pw.println(prefix + "disable-text [group...]");
+ pw.println(prefix + " Disable logcat logging for given groups.");
+ }
+}
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 7a736cc..c39602032 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
@@ -27,6 +27,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ProtoLogController;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -699,6 +700,7 @@
Optional<ActivityEmbeddingController> activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
+ ProtoLogController protoLogController,
@ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) {
return new Object();
}
@@ -714,4 +716,12 @@
static ShellCommandHandler provideShellCommandHandler() {
return new ShellCommandHandler();
}
+
+ @WMSingleton
+ @Provides
+ static ProtoLogController provideProtoLogController(
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler) {
+ return new ProtoLogController(shellInit, shellCommandHandler);
+ }
}
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 31596f3..1564f8f 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
@@ -72,9 +72,9 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
@@ -320,7 +320,7 @@
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -357,15 +357,17 @@
@WMSingleton
@Provides
- static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
- return new PipKeepClearAlgorithm();
+ static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+ return new PhonePipKeepClearAlgorithm(context);
}
@WMSingleton
@Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
- PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
- return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
+ PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) {
+ return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+ pipKeepClearAlgorithm);
}
// Handler is used by Icon.loadDrawableAsync
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index e03421d..4def15d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -37,12 +37,13 @@
* @param activityInfo ActivityInfo tied to the Activity
* @param pictureInPictureParams PictureInPictureParams tied to the Activity
* @param launcherRotation Launcher rotation to calculate the PiP destination bounds
- * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
+ * @param hotseatKeepClearArea Bounds of Hotseat to avoid used to calculate PiP destination
+ bounds
* @return destination bounds the PiP window should land into
*/
Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
in PictureInPictureParams pictureInPictureParams,
- int launcherRotation, int shelfHeight) = 1;
+ int launcherRotation, in Rect hotseatKeepClearArea) = 1;
/**
* Notifies the swiping Activity to PiP onto home transition is finished
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index 7397e52..cd61dbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -47,6 +47,7 @@
private final @NonNull PipBoundsState mPipBoundsState;
private final PipSnapAlgorithm mSnapAlgorithm;
+ private final PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private float mDefaultSizePercent;
private float mMinAspectRatioForMinSize;
@@ -60,9 +61,11 @@
protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
- @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+ @NonNull PipSnapAlgorithm pipSnapAlgorithm,
+ @NonNull PipKeepClearAlgorithm pipKeepClearAlgorithm) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm;
+ mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -129,8 +132,21 @@
return getDefaultBounds(INVALID_SNAP_FRACTION, null /* size */);
}
- /** Returns the destination bounds to place the PIP window on entry. */
+ /**
+ * Returns the destination bounds to place the PIP window on entry.
+ * If there are any keep clear areas registered, the position will try to avoid occluding them.
+ */
public Rect getEntryDestinationBounds() {
+ Rect entryBounds = getEntryDestinationBoundsIgnoringKeepClearAreas();
+ Rect insets = new Rect();
+ getInsetBounds(insets);
+ return mPipKeepClearAlgorithm.findUnoccludedPosition(entryBounds,
+ mPipBoundsState.getRestrictedKeepClearAreas(),
+ mPipBoundsState.getUnrestrictedKeepClearAreas(), insets);
+ }
+
+ /** Returns the destination bounds to place the PIP window on entry. */
+ public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
final Rect destinationBounds = reentryState != null
@@ -138,9 +154,10 @@
: getDefaultBounds();
final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
- return transformBoundsToAspectRatioIfValid(destinationBounds,
+ Rect aspectRatioBounds = transformBoundsToAspectRatioIfValid(destinationBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
useCurrentSize);
+ return aspectRatioBounds;
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java
new file mode 100644
index 0000000..e3495e1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java
@@ -0,0 +1,53 @@
+/*
+ * 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.pip;
+
+import android.graphics.Rect;
+
+import java.util.Set;
+
+/**
+ * Interface for interacting with keep clear algorithm used to move PiP window out of the way of
+ * keep clear areas.
+ */
+public interface PipKeepClearAlgorithm {
+
+ /**
+ * Adjust the position of picture in picture window based on the registered keep clear areas.
+ * @param pipBoundsState state of the PiP to use for the calculations
+ * @param pipBoundsAlgorithm algorithm implementation used to get the entry destination bounds
+ * @return
+ */
+ default Rect adjust(PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm) {
+ return pipBoundsState.getBounds();
+ }
+
+ /**
+ * Calculate the bounds so that none of the keep clear areas are occluded, while the bounds stay
+ * within the allowed bounds. If such position is not feasible, return original bounds.
+ * @param defaultBounds initial bounds used in the calculation
+ * @param restrictedKeepClearAreas registered restricted keep clear areas
+ * @param unrestrictedKeepClearAreas registered unrestricted keep clear areas
+ * @param allowedBounds bounds that define the allowed space for the output, result will always
+ * be inside those bounds
+ * @return bounds that don't cover any of the keep clear areas and are within allowed bounds
+ */
+ default Rect findUnoccludedPosition(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas, Rect allowedBounds) {
+ return defaultBounds;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index 1a4be3b..c6b5ce9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -88,6 +88,11 @@
return isInPip(mState);
}
+ /** Returns true if activity has fully entered PiP mode. */
+ public boolean hasEnteredPip() {
+ return hasEnteredPip(mState);
+ }
+
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
}
@@ -120,6 +125,11 @@
return state >= TASK_APPEARED && state != EXITING_PIP;
}
+ /** Returns true if activity has fully entered PiP mode. */
+ public static boolean hasEnteredPip(@TransitionState int state) {
+ return state == ENTERED_PIP;
+ }
+
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
new file mode 100644
index 0000000..6dd02e4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -0,0 +1,147 @@
+/*
+ * 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.pip.phone;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.ArraySet;
+import android.view.Gravity;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+
+import java.util.Set;
+
+/**
+ * Calculates the adjusted position that does not occlude keep clear areas.
+ */
+public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
+
+ protected int mKeepClearAreasPadding;
+
+ public PhonePipKeepClearAlgorithm(Context context) {
+ reloadResources(context);
+ }
+
+ private void reloadResources(Context context) {
+ final Resources res = context.getResources();
+ mKeepClearAreasPadding = res.getDimensionPixelSize(R.dimen.pip_keep_clear_areas_padding);
+ }
+
+ /**
+ * Adjusts the current position of PiP to avoid occluding keep clear areas. This will push PiP
+ * towards the closest edge and then apply calculations to avoid occluding keep clear areas.
+ */
+ public Rect adjust(PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm) {
+ Rect startingBounds = pipBoundsState.getBounds().isEmpty()
+ ? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
+ : pipBoundsState.getBounds();
+ float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
+ int verticalGravity;
+ int horizontalGravity;
+ if (snapFraction < 1.5f || snapFraction >= 3.5f) {
+ verticalGravity = Gravity.NO_GRAVITY;
+ } else {
+ verticalGravity = Gravity.BOTTOM;
+ }
+ if (snapFraction >= 0.5f && snapFraction < 2.5f) {
+ horizontalGravity = Gravity.RIGHT;
+ } else {
+ horizontalGravity = Gravity.LEFT;
+ }
+ // push the bounds based on the gravity
+ Rect insets = new Rect();
+ pipBoundsAlgorithm.getInsetBounds(insets);
+ if (pipBoundsState.isImeShowing()) {
+ insets.bottom -= pipBoundsState.getImeHeight();
+ }
+ Rect pushedBounds = new Rect(startingBounds);
+ if (verticalGravity == Gravity.BOTTOM) {
+ pushedBounds.offsetTo(pushedBounds.left,
+ insets.bottom - pushedBounds.height());
+ }
+ if (horizontalGravity == Gravity.RIGHT) {
+ pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top);
+ } else {
+ pushedBounds.offsetTo(insets.left, pushedBounds.top);
+ }
+ return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(),
+ pipBoundsState.getUnrestrictedKeepClearAreas(), insets);
+ }
+
+ /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
+ public Rect findUnoccludedPosition(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas, Rect allowedBounds) {
+ if (restrictedKeepClearAreas.isEmpty() && unrestrictedKeepClearAreas.isEmpty()) {
+ return defaultBounds;
+ }
+ Set<Rect> keepClearAreas = new ArraySet<>();
+ if (!restrictedKeepClearAreas.isEmpty()) {
+ keepClearAreas.addAll(restrictedKeepClearAreas);
+ }
+ if (!unrestrictedKeepClearAreas.isEmpty()) {
+ keepClearAreas.addAll(unrestrictedKeepClearAreas);
+ }
+ Rect outBounds = new Rect(defaultBounds);
+ for (Rect r : keepClearAreas) {
+ Rect tmpRect = new Rect(r);
+ // add extra padding to the keep clear area
+ tmpRect.inset(-mKeepClearAreasPadding, -mKeepClearAreasPadding);
+ if (Rect.intersects(r, outBounds)) {
+ if (tryOffsetUp(outBounds, tmpRect, allowedBounds)) continue;
+ if (tryOffsetLeft(outBounds, tmpRect, allowedBounds)) continue;
+ if (tryOffsetDown(outBounds, tmpRect, allowedBounds)) continue;
+ if (tryOffsetRight(outBounds, tmpRect, allowedBounds)) continue;
+ }
+ }
+ return outBounds;
+ }
+
+ private static boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
+ return tryOffset(rectToMove, rectToAvoid, allowedBounds,
+ rectToAvoid.left - rectToMove.right, 0);
+ }
+
+ private static boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
+ return tryOffset(rectToMove, rectToAvoid, allowedBounds,
+ rectToAvoid.right - rectToMove.left, 0);
+ }
+
+ private static boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
+ return tryOffset(rectToMove, rectToAvoid, allowedBounds,
+ 0, rectToAvoid.top - rectToMove.bottom);
+ }
+
+ private static boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
+ return tryOffset(rectToMove, rectToAvoid, allowedBounds,
+ 0, rectToAvoid.bottom - rectToMove.top);
+ }
+
+ private static boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds,
+ int dx, int dy) {
+ Rect tmp = new Rect(rectToMove);
+ tmp.offset(dx, dy);
+ if (!Rect.intersects(rectToAvoid, tmp) && allowedBounds.contains(tmp)) {
+ rectToMove.offsetTo(tmp.left, tmp.top);
+ return true;
+ }
+ return false;
+ }
+}
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 ac3407d..6c9a6b6 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
@@ -43,6 +43,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
@@ -79,6 +80,7 @@
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -110,6 +112,14 @@
UserChangeListener {
private static final String TAG = "PipController";
+ private boolean mEnablePipKeepClearAlgorithm =
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
+ @VisibleForTesting
+ void setEnablePipKeepClearAlgorithm(boolean value) {
+ mEnablePipKeepClearAlgorithm = value;
+ }
+
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -262,7 +272,17 @@
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mPipBoundsState.getDisplayId() == displayId) {
- mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ if (mEnablePipKeepClearAlgorithm) {
+ mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ // only move if already in pip, other transitions account for keep clear
+ // areas
+ if (mPipTransitionState.hasEnteredPip()) {
+ Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
+ mPipBoundsAlgorithm);
+ mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
+ mEnterAnimationDuration, null);
+ }
+ }
}
}
};
@@ -759,8 +779,16 @@
private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
- int launcherRotation, int shelfHeight) {
- setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
+ int launcherRotation, Rect hotseatKeepClearArea) {
+
+ if (mEnablePipKeepClearAlgorithm) {
+ // pre-emptively add the keep clear area for Hotseat, so that it is taken into account
+ // when calculating the entry destination bounds of PiP window
+ mPipBoundsState.getRestrictedKeepClearAreas().add(hotseatKeepClearArea);
+ } else {
+ int shelfHeight = hotseatKeepClearArea.height();
+ setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
+ }
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
@@ -1059,12 +1087,12 @@
@Override
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams, int launcherRotation,
- int shelfHeight) {
+ Rect keepClearArea) {
Rect[] result = new Rect[1];
executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
(controller) -> {
result[0] = controller.startSwipePipToHome(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, shelfHeight);
+ pictureInPictureParams, launcherRotation, keepClearArea);
}, true /* blocking */);
return result[0];
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
deleted file mode 100644
index 78084fa..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
+++ /dev/null
@@ -1,97 +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.pip.phone;
-
-import android.graphics.Rect;
-import android.util.ArraySet;
-
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-
-import java.util.Set;
-
-/**
- * Calculates the adjusted position that does not occlude keep clear areas.
- */
-public class PipKeepClearAlgorithm {
-
- /**
- * Adjusts the current position of PiP to avoid occluding keep clear areas. If the user has
- * moved PiP manually, the unmodified current position will be returned instead.
- */
- public Rect adjust(PipBoundsState boundsState, PipBoundsAlgorithm boundsAlgorithm) {
- if (boundsState.hasUserResizedPip()) {
- return boundsState.getBounds();
- }
- return adjust(boundsAlgorithm.getEntryDestinationBounds(),
- boundsState.getRestrictedKeepClearAreas(),
- boundsState.getUnrestrictedKeepClearAreas(), boundsState.getDisplayBounds());
- }
-
- /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
- public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
- Set<Rect> unrestrictedKeepClearAreas, Rect displayBounds) {
- if (restrictedKeepClearAreas.isEmpty()) {
- return defaultBounds;
- }
- Set<Rect> keepClearAreas = new ArraySet<>();
- if (!restrictedKeepClearAreas.isEmpty()) {
- keepClearAreas.addAll(restrictedKeepClearAreas);
- }
- Rect outBounds = new Rect(defaultBounds);
- for (Rect r : keepClearAreas) {
- if (Rect.intersects(r, outBounds)) {
- if (tryOffsetUp(outBounds, r, displayBounds)) continue;
- if (tryOffsetLeft(outBounds, r, displayBounds)) continue;
- if (tryOffsetDown(outBounds, r, displayBounds)) continue;
- if (tryOffsetRight(outBounds, r, displayBounds)) continue;
- }
- }
- return outBounds;
- }
-
- private boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
- return tryOffset(rectToMove, rectToAvoid, displayBounds,
- rectToAvoid.left - rectToMove.right, 0);
- }
-
- private boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
- return tryOffset(rectToMove, rectToAvoid, displayBounds,
- rectToAvoid.right - rectToMove.left, 0);
- }
-
- private boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
- return tryOffset(rectToMove, rectToAvoid, displayBounds,
- 0, rectToAvoid.top - rectToMove.bottom);
- }
-
- private boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
- return tryOffset(rectToMove, rectToAvoid, displayBounds,
- 0, rectToAvoid.bottom - rectToMove.top);
- }
-
- private boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect displayBounds,
- int dx, int dy) {
- Rect tmp = new Rect(rectToMove);
- tmp.offset(dx, dy);
- if (!Rect.intersects(rectToAvoid, tmp) && displayBounds.contains(tmp)) {
- rectToMove.offsetTo(tmp.left, tmp.top);
- return true;
- }
- return false;
- }
-}
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 a2eadcd..ce34d2f 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
@@ -39,6 +39,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,7 +64,8 @@
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
- super(context, tvPipBoundsState, pipSnapAlgorithm);
+ super(context, tvPipBoundsState, pipSnapAlgorithm,
+ new PipKeepClearAlgorithm() {});
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
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 51921e7..3714fe7 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
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.RemoteAnimationAdapter;
@@ -89,7 +90,7 @@
float splitRatio, in RemoteAnimationAdapter adapter) = 11;
/**
- * Start a pair of intent and task using legacy transition system.
+ * Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
@@ -108,4 +109,11 @@
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+ /**
+ * Starts a pair of shortcut and task using legacy transition system.
+ */
+ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
+ in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
+ in RemoteAnimationAdapter adapter) = 15;
}
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 2117b69..169e17b7 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
@@ -40,6 +40,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
@@ -788,6 +789,17 @@
}
@Override
+ public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController,
+ "startShortcutAndTaskWithLegacyTransition", (controller) ->
+ controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
+ shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
+ splitRatio, adapter));
+ }
+
+ @Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio,
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 c08aa5a6..8d405f4 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
@@ -69,11 +69,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
@@ -85,7 +85,6 @@
import android.util.Slog;
import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -531,133 +530,135 @@
void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
- startWithLegacyTransition(mainTaskId, sideTaskId, null /* pendingIntent */,
- null /* fillInIntent */, mainOptions, sideOptions, sidePosition, splitRatio,
- adapter);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (sideOptions == null) sideOptions = new Bundle();
+ addActivityOptions(sideOptions, mSideStage);
+ wct.startTask(sideTaskId, sideOptions);
+
+ startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter);
}
/** Start an intent and a task ordered by {@code intentFirst}. */
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
- startWithLegacyTransition(taskId, INVALID_TASK_ID, pendingIntent, fillInIntent,
- mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (sideOptions == null) sideOptions = new Bundle();
+ addActivityOptions(sideOptions, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+
+ startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter);
}
- private void startWithLegacyTransition(int mainTaskId, int sideTaskId,
- @Nullable PendingIntent pendingIntent, @Nullable Intent fillInIntent,
- @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
+ void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
- final boolean withIntent = pendingIntent != null && fillInIntent != null;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (sideOptions == null) sideOptions = new Bundle();
+ addActivityOptions(sideOptions, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
+
+ startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter);
+ }
+
+ private void startWithLegacyTransition(WindowContainerTransaction sideWct, int mainTaskId,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter) {
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
+ mSplitLayout.setDivideRatio(splitRatio);
+
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = true;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
- prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
- // Need to add another wrapper here in shell so that we can inject the divider bar
- // and also manage the process elevation via setRunningRemote
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
@Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- RemoteAnimationTarget[] augmentedNonApps =
- new RemoteAnimationTarget[nonApps.length + 1];
- for (int i = 0; i < nonApps.length; ++i) {
- augmentedNonApps[i] = nonApps[i];
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
}
- augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+
+ // The surfaces of splitting tasks were placed with window bounds when preparing the
+ // transition, so update divider surface separately.
+ final RemoteAnimationTarget dividerTarget = getDividerBarLegacyTarget();
+ mSplitLayout.getRefDividerBounds(mTempRect1);
+ t.setLayer(dividerTarget.leash, Integer.MAX_VALUE)
+ .setPosition(dividerTarget.leash, mTempRect1.left, mTempRect1.top);
+ setDividerVisibility(true, t);
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
IRemoteAnimationFinishedCallback wrapCallback =
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
- onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct);
+ onRemoteAnimationFinished(apps);
finishedCallback.onAnimationFinished();
}
};
Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
try {
adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
- augmentedNonApps, wrapCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
- onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct);
- try {
- adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
+ ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ dividerTarget), wrapCallback);
} catch (RemoteException e) {
Slog.e(TAG, "Error starting remote animation", e);
}
}
};
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
- wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- mainOptions = mainActivityOptions.toBundle();
- }
-
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
setSideStagePosition(sidePosition, wct);
-
- mSplitLayout.setDivideRatio(splitRatio);
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 */);
}
+
+ if (mainOptions == null) mainOptions = new Bundle();
+ addActivityOptions(mainOptions, mMainStage);
+ wct.startTask(mainTaskId, mainOptions);
+ wct.merge(sideWct, true);
+
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
- // Make sure the launch options will put tasks in the corresponding split roots
- addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
-
- // Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- if (withIntent) {
- wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
- } else {
- wct.startTask(sideTaskId, sideOptions);
- }
-
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- });
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
- private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
- WindowContainerTransaction evictWct) {
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
- // If any stage has no child after animation finished, it means that split will display
- // nothing, such status will happen if task and intent is same app but not support
- // multi-instance, we should exit split and expand that app as full screen.
- if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
- mMainExecutor.execute(() ->
- exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- } else {
- mSyncQueue.queue(evictWct);
+ if (apps == null || apps.length == 0) return;
+
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ return;
}
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
}
/**
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 2f41aa9..3e3a864 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
@@ -66,6 +66,7 @@
final DisplayController mDisplayController;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
+ final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
@@ -104,8 +105,8 @@
RunningTaskInfo taskInfo,
SurfaceControl taskSurface) {
this(context, displayController, taskOrganizer, taskInfo, taskSurface,
- SurfaceControl.Builder::new, WindowContainerTransaction::new,
- new SurfaceControlViewHostFactory() {});
+ SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+ WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
@@ -115,6 +116,7 @@
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
@@ -123,6 +125,7 @@
mTaskInfo = taskInfo;
mTaskSurface = taskSurface;
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
+ mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
@@ -320,19 +323,28 @@
mCaptionWindowManager = null;
+ final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ boolean released = false;
if (mCaptionContainerSurface != null) {
- mCaptionContainerSurface.release();
+ t.remove(mCaptionContainerSurface);
mCaptionContainerSurface = null;
+ released = true;
}
if (mDecorationContainerSurface != null) {
- mDecorationContainerSurface.release();
+ t.remove(mDecorationContainerSurface);
mDecorationContainerSurface = null;
+ released = true;
}
if (mTaskBackgroundSurface != null) {
- mTaskBackgroundSurface.release();
+ t.remove(mTaskBackgroundSurface);
mTaskBackgroundSurface = null;
+ released = true;
+ }
+
+ if (released) {
+ t.apply();
}
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 9ba5166..61ac498 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -132,7 +132,7 @@
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
- current.visibleRegion.coversAtMost(previous.visibleRegion.region)
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 0059846..262e429 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -64,7 +64,7 @@
initializeMockResources();
mPipBoundsState = new PipBoundsState(mContext);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
- new PipSnapAlgorithm());
+ new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
mPipBoundsState.setDisplayLayout(
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));
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 579638d..9088077 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
@@ -98,7 +98,7 @@
mPipBoundsState = new PipBoundsState(mContext);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
- new PipSnapAlgorithm());
+ new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext,
mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
similarity index 64%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
index e0f7e35..4d7e9e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
@@ -34,61 +34,61 @@
import java.util.Set;
/**
- * Unit tests against {@link PipKeepClearAlgorithm}.
+ * Unit tests against {@link PhonePipKeepClearAlgorithm}.
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipKeepClearAlgorithmTest extends ShellTestCase {
+public class PhonePipKeepClearAlgorithmTest extends ShellTestCase {
- private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+ private PhonePipKeepClearAlgorithm mPipKeepClearAlgorithm;
private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000);
@Before
public void setUp() throws Exception {
- mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
+ mPipKeepClearAlgorithm = new PhonePipKeepClearAlgorithm(mContext);
}
@Test
- public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
+ public void findUnoccludedPosition_withCollidingRestrictedKeepClearArea_movesBounds() {
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(50, 50, 150, 150);
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
- Set.of(), DISPLAY_BOUNDS);
+ final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds,
+ Set.of(keepClearRect), Set.of(), DISPLAY_BOUNDS);
assertFalse(outBounds.contains(keepClearRect));
}
@Test
- public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
+ public void findUnoccludedPosition_withNonCollidingRestrictedKeepClearArea_boundsUnchanged() {
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(100, 100, 150, 150);
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
- Set.of(), DISPLAY_BOUNDS);
+ final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds,
+ Set.of(keepClearRect), Set.of(), DISPLAY_BOUNDS);
assertEquals(inBounds, outBounds);
}
@Test
- public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ public void findUnoccludedPosition_withCollidingUnrestrictedKeepClearArea_moveBounds() {
// TODO(b/183746978): update this test to accommodate for the updated algorithm
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(50, 50, 150, 150);
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds, Set.of(),
Set.of(keepClearRect), DISPLAY_BOUNDS);
- assertEquals(inBounds, outBounds);
+ assertFalse(outBounds.contains(keepClearRect));
}
@Test
- public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ public void findUnoccludedPosition_withNonCollidingUnrestrictedKeepClearArea_boundsUnchanged() {
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(100, 100, 150, 150);
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds, Set.of(),
Set.of(keepClearRect), DISPLAY_BOUNDS);
assertEquals(inBounds, outBounds);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index eb5726b..1b5091f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -64,6 +64,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -85,7 +86,7 @@
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
- @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
+ @Mock private PhonePipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -267,7 +268,20 @@
}
@Test
- public void onKeepClearAreasChanged_updatesPipBoundsState() {
+ public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
+ final int displayId = 1;
+ final Rect keepClearArea = new Rect(0, 0, 10, 10);
+ when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+
+ mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
+ displayId, Set.of(keepClearArea), Set.of());
+
+ verify(mMockPipBoundsState, never()).setKeepClearAreas(Mockito.anySet(), Mockito.anySet());
+ }
+
+ @Test
+ public void onKeepClearAreasChanged_featureEnabled_updatesPipBoundsState() {
+ mPipController.setEnablePipKeepClearAlgorithm(true);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
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 dd10aa7..dba037d 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
@@ -36,6 +36,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -87,8 +88,10 @@
MockitoAnnotations.initMocks(this);
mPipBoundsState = new PipBoundsState(mContext);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
+ final PipKeepClearAlgorithm pipKeepClearAlgorithm =
+ new PipKeepClearAlgorithm() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
- mPipBoundsState, pipSnapAlgorithm);
+ mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index ecefd89..474d6aa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -34,6 +34,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -104,7 +105,8 @@
MockitoAnnotations.initMocks(this);
mPipBoundsState = new PipBoundsState(mContext);
mPipSnapAlgorithm = new PipSnapAlgorithm();
- mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm);
+ mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
+ new PipKeepClearAlgorithm() {});
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
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 1b74d7d..ab6ac94 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
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
@@ -59,6 +60,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import java.util.ArrayList;
@@ -96,6 +98,8 @@
@Mock
private WindowContainerTransaction mMockWindowContainerTransaction;
+ private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
+ new ArrayList<>();
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
@@ -265,6 +269,9 @@
createMockSurfaceControlBuilder(captionContainerSurface);
mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+
final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
new ActivityManager.TaskDescription.Builder()
.setBackgroundColor(Color.YELLOW);
@@ -287,19 +294,19 @@
windowDecor.relayout(taskInfo);
verify(mMockSurfaceControlViewHost, never()).release();
- verify(decorContainerSurface, never()).release();
- verify(taskBackgroundSurface, never()).release();
- verify(captionContainerSurface, never()).release();
+ verify(t, never()).apply();
verify(mMockWindowContainerTransaction, never())
.removeInsetsProvider(eq(taskInfo.token), any());
taskInfo.isVisible = false;
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlViewHost).release();
- verify(decorContainerSurface).release();
- verify(taskBackgroundSurface).release();
- verify(captionContainerSurface).release();
+ final InOrder releaseOrder = inOrder(t, mMockSurfaceControlViewHost);
+ releaseOrder.verify(mMockSurfaceControlViewHost).release();
+ releaseOrder.verify(t).remove(captionContainerSurface);
+ releaseOrder.verify(t).remove(decorContainerSurface);
+ releaseOrder.verify(t).remove(taskBackgroundSurface);
+ releaseOrder.verify(t).apply();
verify(mMockWindowContainerTransaction).removeInsetsProvider(eq(taskInfo.token), any());
}
@@ -351,21 +358,30 @@
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface, new MockSurfaceControlBuilderSupplier(),
+ taskInfo, testSurface,
+ new MockObjectSupplier<>(mMockSurfaceControlBuilders,
+ () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
+ new MockObjectSupplier<>(mMockSurfaceControlTransactions,
+ () -> mock(SurfaceControl.Transaction.class)),
() -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
}
- private class MockSurfaceControlBuilderSupplier implements Supplier<SurfaceControl.Builder> {
+ private class MockObjectSupplier<T> implements Supplier<T> {
+ private final List<T> mObjects;
+ private final Supplier<T> mDefaultSupplier;
private int mNumOfCalls = 0;
+ private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
+ mObjects = objects;
+ mDefaultSupplier = defaultSupplier;
+ }
+
@Override
- public SurfaceControl.Builder get() {
- final SurfaceControl.Builder builder =
- mNumOfCalls < mMockSurfaceControlBuilders.size()
- ? mMockSurfaceControlBuilders.get(mNumOfCalls)
- : createMockSurfaceControlBuilder(mock(SurfaceControl.class));
+ public T get() {
+ final T mock = mNumOfCalls < mObjects.size()
+ ? mObjects.get(mNumOfCalls) : mDefaultSupplier.get();
++mNumOfCalls;
- return builder;
+ return mock;
}
}
@@ -383,11 +399,12 @@
ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
- surfaceControlBuilderSupplier, windowContainerTransactionSupplier,
- surfaceControlViewHostFactory);
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ windowContainerTransactionSupplier, surfaceControlViewHostFactory);
}
@Override
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index f88f46f..8084254 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -183,5 +183,7 @@
<!-- face scanning view id -->
<item type="id" name="face_scanning_anim"/>
+
+ <item type="id" name="qqs_tile_layout"/>
</resources>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index 0fac76d..f3866c0 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -29,17 +29,15 @@
app:percentX="0"
app:percentY="0"
app:framePosition="49"
- app:percentWidth="1"
- app:percentHeight="1"
+ app:sizePercent="0"
app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
app:percentY="0.51"
+ app:sizePercent="1"
app:framePosition="51"
- app:percentWidth="1"
- app:percentHeight="1"
app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyAttribute
@@ -64,6 +62,7 @@
app:percentX="0"
app:percentY="0"
app:framePosition="50"
+ app:sizePercent="0"
app:curveFit="linear"
app:motionTarget="@id/statusIcons" />
<KeyPosition
@@ -71,6 +70,7 @@
app:percentX="1"
app:percentY="0.51"
app:framePosition="51"
+ app:sizePercent="1"
app:curveFit="linear"
app:motionTarget="@id/statusIcons" />
<KeyAttribute
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 112b1e8..46724ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -54,7 +54,9 @@
@Override
public TileLayout getOrCreateTileLayout() {
- return new QQSSideLabelTileLayout(mContext);
+ QQSSideLabelTileLayout layout = new QQSSideLabelTileLayout(mContext);
+ layout.setId(R.id.qqs_tile_layout);
+ return layout;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index e992440..04621168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -52,7 +52,6 @@
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
@@ -76,7 +75,6 @@
import com.android.systemui.tracing.ProtoTracer;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -152,7 +150,6 @@
private static final int MSG_TRACING_STATE_CHANGED = 54 << MSG_SHIFT;
private static final int MSG_SUPPRESS_AMBIENT_DISPLAY = 55 << MSG_SHIFT;
private static final int MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION = 56 << MSG_SHIFT;
- private static final int MSG_HANDLE_WINDOW_MANAGER_LOGGING_COMMAND = 57 << MSG_SHIFT;
//TODO(b/169175022) Update name and when feature name is locked.
private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT;
private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
@@ -425,11 +422,6 @@
default void requestWindowMagnificationConnection(boolean connect) { }
/**
- * Handles a window manager shell logging command.
- */
- default void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd) {}
-
- /**
* @see IStatusBar#setNavigationBarLumaSamplingEnabled(int, boolean)
*/
default void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {}
@@ -1143,17 +1135,6 @@
}
@Override
- public void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd) {
- synchronized (mLock) {
- SomeArgs internalArgs = SomeArgs.obtain();
- internalArgs.arg1 = args;
- internalArgs.arg2 = outFd;
- mHandler.obtainMessage(MSG_HANDLE_WINDOW_MANAGER_LOGGING_COMMAND, internalArgs)
- .sendToTarget();
- }
- }
-
- @Override
public void suppressAmbientDisplay(boolean suppress) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_SUPPRESS_AMBIENT_DISPLAY, suppress).sendToTarget();
@@ -1637,18 +1618,6 @@
mCallbacks.get(i).requestWindowMagnificationConnection((Boolean) msg.obj);
}
break;
- case MSG_HANDLE_WINDOW_MANAGER_LOGGING_COMMAND:
- args = (SomeArgs) msg.obj;
- try (ParcelFileDescriptor pfd = (ParcelFileDescriptor) args.arg2) {
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).handleWindowManagerLoggingCommand(
- (String[]) args.arg1, pfd);
- }
- } catch (IOException e) {
- Log.e(TAG, "Failed to handle logging command", e);
- }
- args.recycle();
- break;
case MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).setNavigationBarLumaSamplingEnabled(msg.arg1,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index a4a59fc..3961a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -34,7 +34,6 @@
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
@@ -62,12 +61,10 @@
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
-import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -336,44 +333,7 @@
if (mShell.handleCommand(args, pw)) {
return;
}
- // Handle logging commands if provided
- if (handleLoggingCommand(args, pw)) {
- return;
- }
// Dump WMShell stuff here if no commands were handled
mShell.dump(pw);
}
-
- @Override
- public void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd) {
- PrintWriter pw = new PrintWriter(new ParcelFileDescriptor.AutoCloseOutputStream(outFd));
- handleLoggingCommand(args, pw);
- pw.flush();
- pw.close();
- }
-
- private boolean handleLoggingCommand(String[] args, PrintWriter pw) {
- ShellProtoLogImpl protoLogImpl = ShellProtoLogImpl.getSingleInstance();
- for (int i = 0; i < args.length; i++) {
- switch (args[i]) {
- case "enable-text": {
- String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
- int result = protoLogImpl.startTextLogging(groups, pw);
- if (result == 0) {
- pw.println("Starting logging on groups: " + Arrays.toString(groups));
- }
- return true;
- }
- case "disable-text": {
- String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
- int result = protoLogImpl.stopTextLogging(groups, pw);
- if (result == 0) {
- pw.println("Stopping logging on groups: " + Arrays.toString(groups));
- }
- return true;
- }
- }
- }
- return false;
- }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0a081bf..0c4ea97 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1005,7 +1005,9 @@
mSfxHelper = new SoundEffectsHelper(mContext);
- mSpatializerHelper = new SpatializerHelper(this, mAudioSystem);
+ final boolean headTrackingDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
+ mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index c143675..8e8fd05 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -169,9 +169,20 @@
//------------------------------------------------------
// initialization
- SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) {
+ @SuppressWarnings("StaticAssignmentInConstructor")
+ SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
+ boolean headTrackingEnabledByDefault) {
mAudioService = mother;
mASA = asa;
+ // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
+ // constructed here is the factory for SADeviceState, thus SADeviceState and its
+ // private static field sHeadTrackingEnabledDefault should never be accessed directly.
+ SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault;
+ }
+
+ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
+ mBinauralSupported = hasBinaural;
+ mTransauralSupported = hasTransaural;
}
synchronized void init(boolean effectExpected, @Nullable String settings) {
@@ -1502,18 +1513,26 @@
}
/*package*/ static final class SADeviceState {
+ private static boolean sHeadTrackingEnabledDefault = false;
final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
final @NonNull String mDeviceAddress;
boolean mEnabled = true; // by default, SA is enabled on any device
boolean mHasHeadTracker = false;
- boolean mHeadTrackerEnabled = true; // by default, if head tracker is present, use it
+ boolean mHeadTrackerEnabled;
static final String SETTING_FIELD_SEPARATOR = ",";
static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
static final String SETTING_DEVICE_SEPARATOR = "\\|";
- SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
+ /**
+ * Constructor
+ * @param deviceType
+ * @param address must be non-null for wireless devices
+ * @throws NullPointerException if a null address is passed for a wireless device
+ */
+ SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
mDeviceType = deviceType;
mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
+ mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
}
@Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 53b8b53..690dd10 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -21,7 +21,6 @@
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
import android.view.InsetsState.InternalInsetsType;
import android.view.InsetsVisibilities;
import android.view.WindowInsetsController.Appearance;
@@ -162,11 +161,6 @@
boolean requestWindowMagnificationConnection(boolean request);
/**
- * Handles a logging command from the WM shell command.
- */
- void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd);
-
- /**
* @see com.android.internal.statusbar.IStatusBar#setNavigationBarLumaSamplingEnabled(int,
* boolean)
*/
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index bec3754..653b51a9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -62,7 +62,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -664,15 +663,6 @@
}
@Override
- public void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd) {
- if (mBar != null) {
- try {
- mBar.handleWindowManagerLoggingCommand(args, outFd);
- } catch (RemoteException ex) { }
- }
- }
-
- @Override
public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
if (mBar != null) {
try {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6fcd285f..d572b1b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -79,6 +79,7 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
@@ -8818,9 +8819,25 @@
* Returns the min aspect ratio of this activity.
*/
float getMinAspectRatio() {
- if (info.applicationInfo == null || !info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
- info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
- && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation()))) {
+ if (info.applicationInfo == null) {
+ return info.getMinAspectRatio();
+ }
+
+ if (!info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO)) {
+ return info.getMinAspectRatio();
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+ && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation())) {
+ return info.getMinAspectRatio();
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
+ && getParent().getConfiguration().orientation == ORIENTATION_PORTRAIT
+ && getParent().getWindowConfiguration().getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN) {
+ // We are using the parent configuration here as this is the most recent one that gets
+ // passed to onConfigurationChanged when a relevant change takes place
return info.getMinAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4e92db2..603a792 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -982,7 +982,9 @@
final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(w);
- if (w.isFocused() && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+
+ if (w.getWindowingMode() != WINDOWING_MODE_PINNED
+ && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
&& preferredModeId != 0) {
mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index ff43a96..c22091b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -34,7 +34,6 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
@@ -47,8 +46,6 @@
import com.android.internal.os.ByteTransferPipe;
import com.android.internal.protolog.ProtoLogImpl;
-import com.android.server.LocalServices;
-import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
@@ -56,7 +53,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
@@ -106,19 +102,11 @@
// trace files can be written.
return mInternal.mWindowTracing.onShellCommand(this);
case "logging":
- String[] args = peekRemainingArgs();
int result = ProtoLogImpl.getSingleInstance().onShellCommand(this);
if (result != 0) {
- // Let the shell try and handle this
- try (ParcelFileDescriptor pfd
- = ParcelFileDescriptor.dup(getOutFileDescriptor())){
- pw.println("Not handled, calling status bar with args: "
- + Arrays.toString(args));
- LocalServices.getService(StatusBarManagerInternal.class)
- .handleWindowManagerLoggingCommand(args, pfd);
- } catch (IOException e) {
- pw.println("Failed to handle logging command: " + e.getMessage());
- }
+ pw.println("Not handled, please use "
+ + "`adb shell dumpsys activity service SystemUIService WMShell` "
+ + "if you are looking for ProtoLog in WMShell");
}
return result;
case "user-rotation":
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index b17c3a1..428eaff 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -55,14 +55,20 @@
mMockAudioService = mock(AudioService.class);
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
- mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem);
+ mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
+ false /*headTrackingEnabledByDefault*/);
}
+ /**
+ * Test that constructing an SADeviceState instance requires a non-null address for a
+ * wireless type, but can take null for a non-wireless type;
+ * @throws Exception
+ */
@Test
public void testSADeviceStateNullAddressCtor() throws Exception {
try {
- SADeviceState devState = new SADeviceState(
- AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
+ SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
+ devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
Assert.fail();
} catch (NullPointerException e) { }
}
@@ -88,11 +94,12 @@
final AudioDeviceAttributes dev1 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
final AudioDeviceAttributes dev2 =
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "C3:P0:beep");
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "C3:PO:beep");
final AudioDeviceAttributes dev3 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");
doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings();
+ mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/);
// test with single device
mSpatHelper.addCompatibleAudioDevice(dev1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 85f85f1..f6ffa91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1640,6 +1640,75 @@
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN})
+ public void testOverrideMinAspectRatioExcludePortraitFullscreen() {
+ setUpDisplaySizeWithApp(2600, 1600);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // Non-resizable portrait activity
+ prepareUnresizable(activity, 0f, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
+ // At first, OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply, because the
+ // display is in landscape
+ assertEquals(1600, activity.getBounds().height());
+ assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ activity.getBounds().width(), 0.5);
+
+ rotateDisplay(activity.mDisplayContent, ROTATION_90);
+ prepareUnresizable(activity, /* maxAspect */ 0, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Now the display is in portrait fullscreen, so the override is applied making the content
+ // fullscreen
+ assertEquals(activity.getBounds(), activity.mDisplayContent.getBounds());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN})
+ public void testOverrideMinAspectRatioExcludePortraitFullscreenNotApplied() {
+ // In this test, the activity is not in fullscreen, so the override is not applied
+ setUpDisplaySizeWithApp(2600, 1600);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, activity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, 0, 1300, 1600);
+ organizer.putTaskToPrimary(mTask, true);
+
+ // Non-resizable portrait activity
+ prepareUnresizable(activity, /* maxAspect */ 0, SCREEN_ORIENTATION_PORTRAIT);
+
+ // OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply here because the
+ // display is not in fullscreen, so OVERRIDE_MIN_ASPECT_RATIO_LARGE applies instead
+ assertEquals(1600, activity.getBounds().height());
+ assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
public void testSplitAspectRatioForUnresizableLandscapeApps() {
// Set up a display in portrait and ignoring orientation request.
int screenWidth = 1400;