Merge "[Output Switcher] Update stop button text" into tm-qpr-dev
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b26504d..5985d6e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,6 +29,8 @@
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -194,6 +196,7 @@
import android.window.SplashScreenView;
import android.window.WindowProviderService;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
@@ -5890,9 +5893,21 @@
final boolean shouldUpdateResources = hasPublicResConfigChange
|| shouldUpdateResources(activityToken, currentResConfig, newConfig,
amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
+
+ // TODO(b/266924897): temporary workaround, remove for U.
+ boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean(
+ R.bool.config_skipActivityRelaunchWhenDocking);
+ int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
+ if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
+ newConfig)) {
+ // If we're not relaunching this activity when docking, we should send the configuration
+ // changed event. Pretend as if the activity is handling uiMode config changes in its
+ // manifest so that we'll report any dock changes.
+ handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
+ }
+
final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
- r != null ? r.mSizeConfigurations : null,
- activity.mActivityInfo.getRealConfigChanged());
+ r != null ? r.mSizeConfigurations : null, handledConfigChanges);
// Nothing significant, don't proceed with updating and reporting.
if (!shouldUpdateResources && !shouldReportChange) {
return null;
@@ -5939,6 +5954,25 @@
}
/**
+ * Returns true if the uiMode configuration changed, and desk mode
+ * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
+ */
+ private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) {
+ boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig);
+
+ // UI mode contains fields other than the UI mode type, so determine if any other fields
+ // changed.
+ boolean uiModeOtherFieldsChanged =
+ (oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK);
+
+ return deskModeChanged && !uiModeOtherFieldsChanged;
+ }
+
+ private static boolean isInDeskUiMode(Configuration config) {
+ return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
+ }
+
+ /**
* Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
* dispatched.
*
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 23f2bdb..1551ce9 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -242,6 +242,12 @@
public boolean isLetterboxDoubleTapEnabled;
/**
+ * Whether the update comes from a letterbox double-tap action from the user or not.
+ * @hide
+ */
+ public boolean isFromLetterboxDoubleTap;
+
+ /**
* If {@link isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position or
* {@link TaskInfo.PROPERTY_VALUE_UNSET} otherwise.
* @hide
@@ -488,7 +494,7 @@
&& isResizeable == that.isResizeable
&& supportsMultiWindow == that.supportsMultiWindow
&& displayAreaFeatureId == that.displayAreaFeatureId
- && isLetterboxDoubleTapEnabled == that.isLetterboxDoubleTapEnabled
+ && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
@@ -520,9 +526,9 @@
return displayId == that.displayId
&& taskId == that.taskId
&& topActivityInSizeCompat == that.topActivityInSizeCompat
+ && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
&& topActivityEligibleForLetterboxEducation
== that.topActivityEligibleForLetterboxEducation
- && isLetterboxDoubleTapEnabled == that.isLetterboxDoubleTapEnabled
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
@@ -583,6 +589,7 @@
displayAreaFeatureId = source.readInt();
cameraCompatControlState = source.readInt();
isLetterboxDoubleTapEnabled = source.readBoolean();
+ isFromLetterboxDoubleTap = source.readBoolean();
topActivityLetterboxVerticalPosition = source.readInt();
topActivityLetterboxHorizontalPosition = source.readInt();
topActivityLetterboxWidth = source.readInt();
@@ -635,6 +642,7 @@
dest.writeInt(displayAreaFeatureId);
dest.writeInt(cameraCompatControlState);
dest.writeBoolean(isLetterboxDoubleTapEnabled);
+ dest.writeBoolean(isFromLetterboxDoubleTap);
dest.writeInt(topActivityLetterboxVerticalPosition);
dest.writeInt(topActivityLetterboxHorizontalPosition);
dest.writeInt(topActivityLetterboxWidth);
@@ -675,6 +683,7 @@
+ " topActivityEligibleForLetterboxEducation= "
+ topActivityEligibleForLetterboxEducation
+ " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled
+ + " isFromDoubleTap= " + isFromLetterboxDoubleTap
+ " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
+ " topActivityLetterboxHorizontalPosition= "
+ topActivityLetterboxHorizontalPosition
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index c8bbb0c1..6f09e79 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1453,6 +1453,16 @@
}
/**
+ * @hide
+ */
+ Configuration[] getSizeAndUiModeConfigurations() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetSizeAndUiModeConfigurations(mObject);
+ }
+ }
+
+ /**
* Change the configuration used when retrieving resources. Not for use by
* applications.
* @hide
@@ -1603,6 +1613,7 @@
private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
+ private static native @Nullable Configuration[] nativeGetSizeAndUiModeConfigurations(long ptr);
private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled);
private static native @Nullable String nativeGetLastResourceResolution(long ptr);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index a03286d3..9b16949 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2207,6 +2207,11 @@
return mResourcesImpl.getSizeConfigurations();
}
+ /** @hide */
+ public Configuration[] getSizeAndUiModeConfigurations() {
+ return mResourcesImpl.getSizeAndUiModeConfigurations();
+ }
+
/**
* Return the compatibility mode information for the application.
* The returned object should be treated as read-only.
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff07291..3bb237a 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -203,6 +203,10 @@
return mAssets.getSizeConfigurations();
}
+ Configuration[] getSizeAndUiModeConfigurations() {
+ return mAssets.getSizeAndUiModeConfigurations();
+ }
+
CompatibilityInfo getCompatibilityInfo() {
return mDisplayAdjustments.getCompatibilityInfo();
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 8c23b21..206ad17 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -93,6 +93,7 @@
jfieldID mScreenWidthDpOffset;
jfieldID mScreenHeightDpOffset;
jfieldID mScreenLayoutOffset;
+ jfieldID mUiMode;
} gConfigurationOffsets;
static struct arraymap_offsets_t {
@@ -1027,10 +1028,11 @@
env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout);
+ env->SetIntField(result, gConfigurationOffsets.mUiMode, config.uiMode);
return result;
}
-static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+static jobjectArray GetSizeAndUiModeConfigurations(JNIEnv* env, jlong ptr) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
auto configurations = assetmanager->GetResourceConfigurations(true /*exclude_system*/,
false /*exclude_mipmap*/);
@@ -1057,6 +1059,14 @@
return array;
}
+static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ return GetSizeAndUiModeConfigurations(env, ptr);
+}
+
+static jobjectArray NativeGetSizeAndUiModeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ return GetSizeAndUiModeConfigurations(env, ptr);
+}
+
static jintArray NativeAttributeResolutionStack(
JNIEnv* env, jclass /*clazz*/, jlong ptr,
jlong theme_ptr, jint xml_style_res,
@@ -1487,6 +1497,8 @@
{"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
{"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
(void*)NativeGetSizeConfigurations},
+ {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeAndUiModeConfigurations},
// Style attribute related methods.
{"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
@@ -1565,6 +1577,7 @@
GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
gConfigurationOffsets.mScreenLayoutOffset =
GetFieldIDOrDie(env, configurationClass, "screenLayout", "I");
+ gConfigurationOffsets.mUiMode = GetFieldIDOrDie(env, configurationClass, "uiMode", "I");
jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap");
gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 46aaa1f..92d2332 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5422,10 +5422,20 @@
split screen. -->
<bool name="config_isWindowManagerCameraCompatTreatmentEnabled">false</bool>
+ <!-- Whether should use split screen aspect ratio for the activity when camera compat treatment
+ is enabled and activity is connected to the camera in fullscreen. -->
+ <bool name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
+ <!-- Docking is a uiMode configuration change and will cause activities to relaunch if it's not
+ handled. If true, the configuration change will be sent but activities will not be
+ relaunched upon docking. Apps with desk resources will behave like normal, since they may
+ expect the relaunch upon the desk uiMode change. -->
+ <bool name="config_skipActivityRelaunchWhenDocking">false</bool>
+
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1a3feb8..d8b261c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4489,7 +4489,9 @@
<java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" />
<java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
<java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
+ <java-symbol type="bool" name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
+ <java-symbol type="bool" name="config_skipActivityRelaunchWhenDocking" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 902c41c..4e10ce8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -156,15 +156,13 @@
void setDontShowReachabilityEducationAgain(TaskInfo taskInfo) {
mCompatUISharedPreferences.edit().putBoolean(
- getDontShowAgainReachabilityEduKey(taskInfo.userId,
- taskInfo.topActivity.getPackageName()), true).apply();
+ getDontShowAgainReachabilityEduKey(taskInfo.userId), true).apply();
}
boolean shouldShowReachabilityEducation(@NonNull TaskInfo taskInfo) {
return getHasSeenLetterboxEducation(taskInfo.userId)
&& !mCompatUISharedPreferences.getBoolean(
- getDontShowAgainReachabilityEduKey(taskInfo.userId,
- taskInfo.topActivity.getPackageName()), /* default= */false);
+ getDontShowAgainReachabilityEduKey(taskInfo.userId), /* default= */false);
}
boolean getHasSeenLetterboxEducation(int userId) {
@@ -206,8 +204,8 @@
}
}
- private static String getDontShowAgainReachabilityEduKey(int userId, String packageName) {
- return HAS_SEEN_REACHABILITY_EDUCATION_KEY_PREFIX + "_" + packageName + "@" + userId;
+ private static String getDontShowAgainReachabilityEduKey(int userId) {
+ return HAS_SEEN_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
}
private static String getDontShowLetterboxEduKey(int userId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index d44b4d8..f65c26a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -21,6 +21,7 @@
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -112,6 +113,14 @@
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mWindowManager.relayout();
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 6223efa..f1b098e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -74,9 +74,7 @@
private boolean mForceUpdate = false;
// We decided to force the visualization of the double-tap animated icons every time the user
- // double-taps. We detect a double-tap checking the previous and current state of
- // mLetterboxVerticalPosition and mLetterboxHorizontalPosition saving the result in this
- // variable.
+ // double-taps.
private boolean mHasUserDoubleTapped;
// When the size of the letterboxed app changes and the icons are visible
@@ -155,11 +153,9 @@
mLetterboxHorizontalPosition = taskInfo.topActivityLetterboxHorizontalPosition;
mTopActivityLetterboxWidth = taskInfo.topActivityLetterboxWidth;
mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
+ mHasUserDoubleTapped = taskInfo.isFromLetterboxDoubleTap;
- mHasUserDoubleTapped =
- mLetterboxVerticalPosition != prevLetterboxVerticalPosition
- || prevLetterboxHorizontalPosition != mLetterboxHorizontalPosition;
- if (mHasUserDoubleTapped) {
+ if (taskInfo.isFromLetterboxDoubleTap) {
// In this case we disable the reachability for the following launch of
// the current application. Anyway because a double tap event happened,
// the reachability education is displayed
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 1187126..4c53f60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -210,7 +210,7 @@
/**
* Quietly cancel the animator by removing the listeners first.
*/
- public static void quietCancel(@NonNull ValueAnimator animator) {
+ static void quietCancel(@NonNull ValueAnimator animator) {
animator.removeAllUpdateListeners();
animator.removeAllListeners();
animator.cancel();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 1dd2ef9..7e9f2c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -147,10 +147,12 @@
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
+ private boolean mIsCancelled;
@Override
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ mIsCancelled = false;
sendOnPipTransitionStarted(direction);
}
@@ -158,6 +160,10 @@
public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ if (mIsCancelled) {
+ sendOnPipTransitionFinished(direction);
+ return;
+ }
final int animationType = animator.getAnimationType();
final Rect destinationBounds = animator.getDestinationBounds();
if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
@@ -196,6 +202,7 @@
public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
+ mIsCancelled = true;
if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
animator::clearContentOverlay, true /* withStartDelay */);
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 9807320..2bd5f1c 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
@@ -779,7 +779,7 @@
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
// cancel any running animator, as it is using stale display layout information
- PipAnimationController.quietCancel(animator);
+ animator.cancel();
}
onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 3f79df6..12ceb0a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -66,6 +66,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashSet;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -118,6 +120,18 @@
mExecutor = new TestShellExecutor();
mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
+ final Set<Integer> mHasSeenSet = new HashSet<>();
+
+ @Override
+ boolean getHasSeenLetterboxEducation(int userId) {
+ return mHasSeenSet.contains(userId);
+ }
+
+ @Override
+ void setSeenLetterboxEducation(int userId) {
+ mHasSeenSet.add(userId);
+ }
+
@Override
protected String getCompatUISharedPreferenceName() {
return TEST_COMPAT_UI_SHARED_PREFERENCES;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 91e1e19..359ef97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.TaskInfo;
@@ -32,7 +31,6 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,10 +69,6 @@
mExecutor = new TestShellExecutor();
}
- @After
- public void tearDown() {
- }
-
@Test
public void testCreateLayout_notEligible_doesNotCreateLayout() {
final ReachabilityEduWindowManager windowManager = createReachabilityEduWindowManager(
@@ -85,20 +79,6 @@
assertNull(windowManager.mLayout);
}
- @Test
- public void testCreateLayout_letterboxPositionChanged_doubleTapIsDetected() {
- // Initial left position
- final TaskInfo initialTaskInfo = createTaskInfoForHorizontalTapping(USER_ID, 0, 1000);
- final ReachabilityEduWindowManager windowManager =
- createReachabilityEduWindowManager(initialTaskInfo);
- // Move to the right
- final TaskInfo newPositionTaskInfo = createTaskInfoForHorizontalTapping(USER_ID, 1, 1000);
- windowManager.updateCompatInfo(newPositionTaskInfo, mTaskListener, /* canShow */ true);
-
- verify(mCompatUIConfiguration).setDontShowReachabilityEducationAgain(newPositionTaskInfo);
- }
-
-
private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
return new ReachabilityEduWindowManager(mContext, taskInfo,
mSyncTransactionQueue, mCallback, mTaskListener, mDisplayLayout,
@@ -113,14 +93,6 @@
/* topActivityLetterboxHeight */ -1);
}
- private static TaskInfo createTaskInfoForHorizontalTapping(int userId,
- int topActivityLetterboxHorizontalPosition, int topActivityLetterboxWidth) {
- return createTaskInfo(userId, /* isLetterboxDoubleTapEnabled */ true,
- /* topActivityLetterboxVerticalPosition */ -1,
- topActivityLetterboxHorizontalPosition, topActivityLetterboxWidth,
- /* topActivityLetterboxHeight */ -1);
- }
-
private static TaskInfo createTaskInfo(int userId, boolean isLetterboxDoubleTapEnabled,
int topActivityLetterboxVerticalPosition, int topActivityLetterboxHorizontalPosition,
int topActivityLetterboxWidth, int topActivityLetterboxHeight) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index bf264f8f..031c3ff 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -107,7 +107,7 @@
private final Map<OnMediaKeyEventSessionChangedListener, Executor>
mMediaKeyEventSessionChangedCallbacks = new HashMap<>();
@GuardedBy("mLock")
- private String mCurMediaKeyEventSessionPackage;
+ private String mCurMediaKeyEventSessionPackage = "";
@GuardedBy("mLock")
private MediaSession.Token mCurMediaKeyEventSession;
@GuardedBy("mLock")
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index efcb6f3..8bff1a1 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -29,9 +29,10 @@
android:format12Hour="@string/dream_time_complication_12_hr_time_format"
android:format24Hour="@string/dream_time_complication_24_hr_time_format"
android:fontFeatureSettings="pnum, lnum"
+ android:includeFontPadding="false"
android:letterSpacing="0.02"
+ android:maxLines="1"
android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
- android:translationY="@dimen/dream_overlay_complication_clock_time_translation_y"
app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
@@ -40,6 +41,7 @@
app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
app:ambientShadowAlpha="0.3"
- />
+ app:removeTextDescent="true"
+ app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9145982..127d67e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1591,7 +1591,6 @@
<dimen name="dream_overlay_bottom_affordance_radius">32dp</dimen>
<dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
<dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
- <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
<dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
<dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
<dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
@@ -1692,6 +1691,7 @@
<dimen name="dream_overlay_clock_ambient_text_shadow_dx">0dp</dimen>
<dimen name="dream_overlay_clock_ambient_text_shadow_dy">0dp</dimen>
<dimen name="dream_overlay_clock_ambient_text_shadow_radius">1dp</dimen>
+ <dimen name="dream_overlay_clock_text_descent_extra_padding">1dp</dimen>
<!-- Shadow for dream overlay status bar complications -->
<dimen name="dream_overlay_status_bar_key_text_shadow_dx">0.5dp</dimen>
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index f3aeaef..84ea6b7 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -40,6 +40,9 @@
<attr name="ambientShadowOffsetX" />
<attr name="ambientShadowOffsetY" />
<attr name="ambientShadowAlpha" />
+ <attr name="removeTextDescent" format="boolean" />
+ <!-- padding to add back when removing text descent so it ensures text is not clipped -->
+ <attr name="textDescentExtraPadding" format="dimension" />
</declare-styleable>
<declare-styleable name="DoubleShadowTextView">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
index f2db129..5a6f184 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
@@ -22,6 +22,7 @@
import com.android.systemui.shared.R
import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+import kotlin.math.floor
/** Extension of [TextClock] which draws two shadows on the text (ambient and key shadows) */
class DoubleShadowTextClock
@@ -89,6 +90,21 @@
ambientShadowOffsetY.toFloat(),
ambientShadowAlpha
)
+ val removeTextDescent =
+ attributes.getBoolean(R.styleable.DoubleShadowTextClock_removeTextDescent, false)
+ val textDescentExtraPadding =
+ attributes.getDimensionPixelSize(
+ R.styleable.DoubleShadowTextClock_textDescentExtraPadding,
+ 0
+ )
+ if (removeTextDescent) {
+ setPaddingRelative(
+ 0,
+ 0,
+ 0,
+ textDescentExtraPadding - floor(paint.fontMetrics.descent.toDouble()).toInt()
+ )
+ }
} finally {
attributes.recycle()
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index f83885b..d6c85fb 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -16,6 +16,8 @@
package com.android.systemui.classifier;
+import static com.android.systemui.classifier.FalsingModule.IS_FOLDABLE_DEVICE;
+
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -30,6 +32,7 @@
import java.util.List;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Acts as a cache and utility class for FalsingClassifiers.
@@ -46,6 +49,7 @@
private BatteryController mBatteryController;
private final FoldStateListener mFoldStateListener;
private final DockManager mDockManager;
+ private boolean mIsFoldableDevice;
private final float mXdpi;
private final float mYdpi;
private final List<SessionListener> mSessionListeners = new ArrayList<>();
@@ -70,7 +74,8 @@
DisplayMetrics displayMetrics,
BatteryController batteryController,
FoldStateListener foldStateListener,
- DockManager dockManager) {
+ DockManager dockManager,
+ @Named(IS_FOLDABLE_DEVICE) boolean isFoldableDevice) {
mXdpi = displayMetrics.xdpi;
mYdpi = displayMetrics.ydpi;
mWidthPixels = displayMetrics.widthPixels;
@@ -78,6 +83,7 @@
mBatteryController = batteryController;
mFoldStateListener = foldStateListener;
mDockManager = dockManager;
+ mIsFoldableDevice = isFoldableDevice;
FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi());
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
@@ -417,7 +423,7 @@
}
public boolean isUnfolded() {
- return Boolean.FALSE.equals(mFoldStateListener.getFolded());
+ return mIsFoldableDevice && Boolean.FALSE.equals(mFoldStateListener.getFolded());
}
/** Implement to be alerted abotu the beginning and ending of falsing tracking. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index 5302af9..c7f3b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -43,6 +43,7 @@
String LONG_TAP_TOUCH_SLOP = "falsing_long_tap_slop";
String DOUBLE_TAP_TOUCH_SLOP = "falsing_double_tap_touch_slop";
String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
+ String IS_FOLDABLE_DEVICE = "falsing_foldable_device";
/** */
@Binds
@@ -89,4 +90,16 @@
static float providesLongTapTouchSlop(ViewConfiguration viewConfiguration) {
return viewConfiguration.getScaledTouchSlop() * 1.25f;
}
+
+ /** */
+ @Provides
+ @Named(IS_FOLDABLE_DEVICE)
+ static boolean providesIsFoldableDevice(@Main Resources resources) {
+ try {
+ return resources.getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates).length != 0;
+ } catch (Resources.NotFoundException e) {
+ return false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 461cacc..0218f45 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -17,10 +17,15 @@
package com.android.systemui.controls.start
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.os.UserHandle
+import android.os.UserManager
import androidx.annotation.WorkerThread
import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
@@ -53,6 +58,8 @@
private val userTracker: UserTracker,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
private val selectedComponentRepository: SelectedComponentRepository,
+ private val userManager: UserManager,
+ private val broadcastDispatcher: BroadcastDispatcher,
) : CoreStartable {
// These two controllers can only be accessed after `start` method once we've checked if the
@@ -71,7 +78,9 @@
}
}
- override fun start() {
+ override fun start() {}
+
+ override fun onBootCompleted() {
if (!controlsComponent.isEnabled()) {
// Controls is disabled, we don't need this anymore
return
@@ -112,11 +121,30 @@
}
private fun bindToPanel() {
+ if (userManager.isUserUnlocked(userTracker.userId)) {
+ bindToPanelInternal()
+ } else {
+ broadcastDispatcher.registerReceiver(
+ receiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (userManager.isUserUnlocked(userTracker.userId)) {
+ bindToPanelInternal()
+ broadcastDispatcher.unregisterReceiver(this)
+ }
+ }
+ },
+ filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ executor = executor,
+ user = userTracker.userHandle,
+ )
+ }
+ }
+
+ private fun bindToPanelInternal() {
val currentSelection = controlsController.getPreferredSelection()
val panels =
- controlsListingController.getCurrentServices().filter { it.panelActivity != null }
- if (
- currentSelection is SelectedItem.PanelItem &&
+ controlsListingController.getCurrentServices().filter { it.panelActivity != null }
+ if (currentSelection is SelectedItem.PanelItem &&
panels.firstOrNull { it.componentName == currentSelection.componentName } != null
) {
controlsController.bindComponentForPanel(currentSelection.componentName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index ae5b799..64e2a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.os.Build
+import android.util.Log
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/**
* Encapsulates app state for the lock screen primary and alternate bouncer.
@@ -231,6 +233,7 @@
primaryBouncerShow
.logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
+ .onEach { Log.d(TAG, "Keyguard Bouncer is ${if (it) "showing" else "hiding."}") }
.launchIn(applicationScope)
primaryBouncerShowingSoon
.logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
@@ -274,5 +277,6 @@
companion object {
private const val NOT_VISIBLE = -1L
+ private const val TAG = "KeyguardBouncerRepositoryImpl"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 0a3b3d3..e9184ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -95,8 +95,7 @@
}
val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
- val show: Flow<Unit> = repository.primaryBouncerShow.filter { it }.map {}
- val hide: Flow<Unit> = repository.primaryBouncerShow.filter { !it }.map {}
+ val isShowing: Flow<Boolean> = repository.primaryBouncerShow
val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 5fcf105..468a6b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -109,36 +109,36 @@
try {
viewModel.setBouncerViewDelegate(delegate)
launch {
- viewModel.show.collect {
- // Reset Security Container entirely.
- securityContainerController.reinflateViewFlipper {
+ viewModel.isShowing.collect { isShowing ->
+ if (isShowing) {
// Reset Security Container entirely.
- view.visibility = View.VISIBLE
+ securityContainerController.reinflateViewFlipper {
+ // Reset Security Container entirely.
+ view.visibility = View.VISIBLE
+ securityContainerController.onBouncerVisibilityChanged(
+ /* isVisible= */ true
+ )
+ securityContainerController.showPrimarySecurityScreen(
+ /* turningOff= */ false
+ )
+ securityContainerController.appear()
+ securityContainerController.onResume(
+ KeyguardSecurityView.SCREEN_ON
+ )
+ }
+ } else {
+ view.visibility = View.INVISIBLE
securityContainerController.onBouncerVisibilityChanged(
- /* isVisible= */ true
+ /* isVisible= */ false
)
- securityContainerController.showPrimarySecurityScreen(
- /* turningOff= */ false
- )
- securityContainerController.appear()
- securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+ securityContainerController.cancelDismissAction()
+ securityContainerController.reset()
+ securityContainerController.onPause()
}
}
}
launch {
- viewModel.hide.collect {
- view.visibility = View.INVISIBLE
- securityContainerController.onBouncerVisibilityChanged(
- /* isVisible= */ false
- )
- securityContainerController.cancelDismissAction()
- securityContainerController.reset()
- securityContainerController.onPause()
- }
- }
-
- launch {
viewModel.startingToHide.collect {
securityContainerController.onStartingToHide()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 0656c9b..9602888 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -40,11 +40,8 @@
/** Can the user interact with the view? */
val isInteractable: Flow<Boolean> = interactor.isInteractable
- /** Observe whether bouncer is showing. */
- val show: Flow<Unit> = interactor.show
-
- /** Observe whether bouncer is hiding. */
- val hide: Flow<Unit> = interactor.hide
+ /** Observe whether bouncer is showing or not. */
+ val isShowing: Flow<Boolean> = interactor.isShowing
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
@@ -70,8 +67,8 @@
/** Observe whether we should update fps is showing. */
val shouldUpdateSideFps: Flow<Unit> =
merge(
- interactor.hide,
- interactor.show,
+ interactor.isShowing.map {},
+ interactor.startingToHide,
interactor.startingDisappearAnimation.filterNotNull().map {}
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index e10d74d..54237ce 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -317,13 +317,16 @@
/**
* Returns the amount of translationY of the media container, during the current guided
- * transformation, if running. If there is no guided transformation running, it will return 0.
+ * transformation, if running. If there is no guided transformation running, it will return -1.
*/
fun getGuidedTransformationTranslationY(): Int {
if (!isCurrentlyInGuidedTransformation()) {
return -1
}
- val startHost = getHost(previousLocation) ?: return 0
+ val startHost = getHost(previousLocation)
+ if (startHost == null || !startHost.visible) {
+ return 0
+ }
return targetBounds.top - startHost.currentBounds.top
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 6e185fe..ac7b4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -25,6 +25,7 @@
import static com.android.systemui.shade.NotificationPanelViewController.FLING_HIDE;
import static com.android.systemui.shade.NotificationPanelViewController.QS_PARALLAX_AMOUNT;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -246,6 +247,12 @@
/** The duration of the notification bounds animation. */
private long mNotificationBoundsAnimationDuration;
+ /** TODO(b/273591201): remove after bug resolved */
+ private int mLastClippingTopBound;
+ private int mLastNotificationsTopPadding;
+ private int mLastNotificationsClippingTopBound;
+ private int mLastNotificationsClippingTopBoundNssl;
+
private final Region mInterceptRegion = new Region();
/** The end bounds of a clipping animation. */
private final Rect mClippingAnimationEndBounds = new Rect();
@@ -610,7 +617,7 @@
float appearAmount = mNotificationStackScrollLayoutController
.calculateAppearFraction(mShadeExpandedHeight);
float startHeight = -getExpansionHeight();
- if (mBarState == StatusBarState.SHADE) {
+ if (mBarState == SHADE) {
// Small parallax as we pull down and clip QS
startHeight = -getExpansionHeight() * QS_PARALLAX_AMOUNT;
}
@@ -1086,6 +1093,7 @@
mClippingAnimationEndBounds.left, fraction);
int animTop = (int) MathUtils.lerp(startTop,
mClippingAnimationEndBounds.top, fraction);
+ logClippingTopBound("interpolated top bound", top);
int animRight = (int) MathUtils.lerp(startRight,
mClippingAnimationEndBounds.right, fraction);
int animBottom = (int) MathUtils.lerp(startBottom,
@@ -1205,6 +1213,8 @@
// the screen without clipping.
return -mAmbientState.getStackTopMargin();
} else {
+ logNotificationsClippingTopBound(qsTop,
+ mNotificationStackScrollLayoutController.getTop());
return qsTop - mNotificationStackScrollLayoutController.getTop();
}
}
@@ -1227,6 +1237,7 @@
/** Calculate top padding for notifications */
public float calculateNotificationsTopPadding(boolean isShadeExpanding,
int keyguardNotificationStaticPadding, float expandedFraction) {
+ float topPadding;
boolean keyguardShowing = mBarState == KEYGUARD;
if (mSplitShadeEnabled) {
return keyguardShowing
@@ -1243,19 +1254,27 @@
int maxQsPadding = getMaxExpansionHeight();
int max = keyguardShowing ? Math.max(
keyguardNotificationStaticPadding, maxQsPadding) : maxQsPadding;
- return (int) MathUtils.lerp((float) getMinExpansionHeight(),
+ topPadding = (int) MathUtils.lerp((float) getMinExpansionHeight(),
(float) max, expandedFraction);
+ logNotificationsTopPadding("keyguard and expandImmediate", topPadding);
+ return topPadding;
} else if (isSizeChangeAnimationRunning()) {
- return Math.max((int) mSizeChangeAnimator.getAnimatedValue(),
+ topPadding = Math.max((int) mSizeChangeAnimator.getAnimatedValue(),
keyguardNotificationStaticPadding);
+ logNotificationsTopPadding("size change animation running", topPadding);
+ return topPadding;
} else if (keyguardShowing) {
// We can only do the smoother transition on Keyguard when we also are not collapsing
// from a scrolled quick settings.
- return MathUtils.lerp((float) keyguardNotificationStaticPadding,
+ topPadding = MathUtils.lerp((float) keyguardNotificationStaticPadding,
(float) (getMaxExpansionHeight()), computeExpansionFraction());
+ logNotificationsTopPadding("keyguard", topPadding);
+ return topPadding;
} else {
- return mQsFrameTranslateController.getNotificationsTopPadding(
+ topPadding = mQsFrameTranslateController.getNotificationsTopPadding(
mExpansionHeight, mNotificationStackScrollLayoutController);
+ logNotificationsTopPadding("default case", topPadding);
+ return topPadding;
}
}
@@ -1302,6 +1321,38 @@
- mAmbientState.getScrollY());
}
+ /** TODO(b/273591201): remove after bug resolved */
+ private void logNotificationsTopPadding(String message, float rawPadding) {
+ int padding = ((int) rawPadding / 10) * 10;
+ if (mBarState != KEYGUARD && padding != mLastNotificationsTopPadding && !mExpanded) {
+ mLastNotificationsTopPadding = padding;
+ mShadeLog.logNotificationsTopPadding(message, padding);
+ }
+ }
+
+ /** TODO(b/273591201): remove after bug resolved */
+ private void logClippingTopBound(String message, int top) {
+ top = (top / 10) * 10;
+ if (mBarState != KEYGUARD && mShadeExpandedFraction == 1
+ && top != mLastClippingTopBound && !mExpanded) {
+ mLastClippingTopBound = top;
+ mShadeLog.logClippingTopBound(message, top);
+ }
+ }
+
+ /** TODO(b/273591201): remove after bug resolved */
+ private void logNotificationsClippingTopBound(int top, int nsslTop) {
+ top = (top / 10) * 10;
+ nsslTop = (nsslTop / 10) * 10;
+ if (mBarState == SHADE && mShadeExpandedFraction == 1
+ && (top != mLastNotificationsClippingTopBound
+ || nsslTop != mLastNotificationsClippingTopBoundNssl) && !mExpanded) {
+ mLastNotificationsClippingTopBound = top;
+ mLastNotificationsClippingTopBoundNssl = nsslTop;
+ mShadeLog.logNotificationsClippingTopBound(top, nsslTop);
+ }
+ }
+
private int calculateTopClippingBound(int qsPanelBottomY) {
int top;
if (mSplitShadeEnabled) {
@@ -1311,6 +1362,7 @@
// If we're transitioning, let's use the actual value. The else case
// can be wrong during transitions when waiting for the keyguard to unlock
top = mTransitionToFullShadePosition;
+ logClippingTopBound("set while transitioning to full shade", top);
} else {
final float notificationTop = getEdgePosition();
if (mBarState == KEYGUARD) {
@@ -1319,8 +1371,10 @@
// this should go away once we unify the stackY position and don't have
// to do this min anymore below.
top = qsPanelBottomY;
+ logClippingTopBound("bypassing keyguard", top);
} else {
top = (int) Math.min(qsPanelBottomY, notificationTop);
+ logClippingTopBound("keyguard default case", top);
}
} else {
top = (int) notificationTop;
@@ -1328,12 +1382,14 @@
}
// TODO (b/265193930): remove dependency on NPVC
top += mPanelViewControllerLazy.get().getOverStretchAmount();
+ logClippingTopBound("including overstretch", top);
// Correction for instant expansion caused by HUN pull down/
float minFraction = mPanelViewControllerLazy.get().getMinFraction();
if (minFraction > 0f && minFraction < 1f) {
float realFraction = (mShadeExpandedFraction
- minFraction) / (1f - minFraction);
top *= MathUtils.saturate(realFraction / minFraction);
+ logClippingTopBound("after adjusted fraction", top);
}
}
return top;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index d34e127..da4944c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -280,4 +280,40 @@
{ "Split shade state changed: split shade ${if (bool1) "enabled" else "disabled"}" }
)
}
+
+ fun logNotificationsTopPadding(message: String, padding: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ int1 = padding
+ },
+ { "QSC NotificationsTopPadding $str1: $int1"}
+ )
+ }
+
+ fun logClippingTopBound(message: String, top: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ int1 = top
+ },
+ { "QSC ClippingTopBound $str1: $int1" }
+ )
+ }
+
+ fun logNotificationsClippingTopBound(top: Int, nsslTop: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ int1 = top
+ int2 = nsslTop
+ },
+ { "QSC NotificationsClippingTopBound set to $int1 - $int2" }
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
index 94cf384..9254617 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
@@ -50,7 +50,7 @@
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
mDataProvider = new FalsingDataProvider(
- displayMetrics, mBatteryController, mFoldStateListener, mDockManager);
+ displayMetrics, mBatteryController, mFoldStateListener, mDockManager, false);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 8eadadf..7e06680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -54,18 +54,18 @@
@Mock
private FoldStateListener mFoldStateListener;
private final DockManagerFake mDockManager = new DockManagerFake();
+ private DisplayMetrics mDisplayMetrics;
@Before
public void setup() {
super.setup();
MockitoAnnotations.initMocks(this);
- DisplayMetrics displayMetrics = new DisplayMetrics();
- displayMetrics.xdpi = 100;
- displayMetrics.ydpi = 100;
- displayMetrics.widthPixels = 1000;
- displayMetrics.heightPixels = 1000;
- mDataProvider = new FalsingDataProvider(
- displayMetrics, mBatteryController, mFoldStateListener, mDockManager);
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplayMetrics.xdpi = 100;
+ mDisplayMetrics.ydpi = 100;
+ mDisplayMetrics.widthPixels = 1000;
+ mDisplayMetrics.heightPixels = 1000;
+ mDataProvider = createWithFoldCapability(false);
}
@After
@@ -345,20 +345,42 @@
}
@Test
- public void test_FoldedState_Folded() {
+ public void test_UnfoldedState_Folded() {
+ FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
when(mFoldStateListener.getFolded()).thenReturn(true);
- assertThat(mDataProvider.isUnfolded()).isFalse();
+ assertThat(falsingDataProvider.isUnfolded()).isFalse();
}
@Test
- public void test_FoldedState_Unfolded() {
+ public void test_UnfoldedState_Unfolded() {
+ FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
when(mFoldStateListener.getFolded()).thenReturn(false);
- assertThat(mDataProvider.isUnfolded()).isTrue();
+ assertThat(falsingDataProvider.isUnfolded()).isTrue();
}
@Test
- public void test_FoldedState_NotFoldable() {
+ public void test_Nonfoldabled_TrueFoldState() {
+ FalsingDataProvider falsingDataProvider = createWithFoldCapability(false);
+ when(mFoldStateListener.getFolded()).thenReturn(true);
+ assertThat(falsingDataProvider.isUnfolded()).isFalse();
+ }
+
+ @Test
+ public void test_Nonfoldabled_FalseFoldState() {
+ FalsingDataProvider falsingDataProvider = createWithFoldCapability(false);
+ when(mFoldStateListener.getFolded()).thenReturn(false);
+ assertThat(falsingDataProvider.isUnfolded()).isFalse();
+ }
+
+ @Test
+ public void test_Nonfoldabled_NullFoldState() {
+ FalsingDataProvider falsingDataProvider = createWithFoldCapability(true);
when(mFoldStateListener.getFolded()).thenReturn(null);
- assertThat(mDataProvider.isUnfolded()).isFalse();
+ assertThat(falsingDataProvider.isUnfolded()).isFalse();
+ }
+
+ private FalsingDataProvider createWithFoldCapability(boolean foldable) {
+ return new FalsingDataProvider(
+ mDisplayMetrics, mBatteryController, mFoldStateListener, mDockManager, foldable);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index bd7e98e..8f65fc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -17,13 +17,18 @@
package com.android.systemui.controls.start
+import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
+import android.os.UserManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
@@ -34,14 +39,20 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
@@ -58,6 +69,8 @@
@Mock private lateinit var controlsListingController: ControlsListingController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
private val preferredPanelsRepository = FakeSelectedComponentRepository()
@@ -67,13 +80,27 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
+ whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true)
fakeExecutor = FakeExecutor(FakeSystemClock())
}
@Test
fun testDisabledNothingIsCalled() {
- createStartable(enabled = false).start()
+ createStartable(enabled = false).apply {
+ start()
+ onBootCompleted()
+ }
+
+ verifyZeroInteractions(controlsController, controlsListingController, userTracker)
+ }
+
+ @Test
+ fun testNothingCalledOnStart() {
+ createStartable(enabled = true).start()
+
+ fakeExecutor.advanceClockToLast()
+ fakeExecutor.runAllReady()
verifyZeroInteractions(controlsController, controlsListingController, userTracker)
}
@@ -84,7 +111,7 @@
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
setUpControlsListingControls(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
@@ -97,7 +124,7 @@
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
setUpControlsListingControls(emptyList())
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
@@ -111,7 +138,7 @@
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
setUpControlsListingControls(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
@@ -126,7 +153,7 @@
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
setUpControlsListingControls(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
@@ -140,7 +167,7 @@
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
setUpControlsListingControls(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
@@ -158,7 +185,7 @@
)
setUpControlsListingControls(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
@@ -176,32 +203,78 @@
)
setUpControlsListingControls(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
}
@Test
- fun testPreferredSelectionIsPanel_bindOnStart() {
+ fun testPreferredSelectionIsPanel_bindOnBoot() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
setUpControlsListingControls(listings)
`when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
}
@Test
+ fun testPreferredSelectionIsPanel_userNotUnlocked_notBind() {
+ whenever(userManager.isUserUnlocked(anyInt())).thenReturn(false)
+
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ setUpControlsListingControls(listings)
+ `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
+
+ createStartable(enabled = true).onBootCompleted()
+ fakeExecutor.runAllReady()
+
+ verify(controlsController, never()).bindComponentForPanel(TEST_COMPONENT_PANEL)
+ }
+
+ @Test
+ fun testPreferredSelectionIsPanel_userNotUnlocked_broadcastRegistered_broadcastSentBinds() {
+ whenever(userManager.isUserUnlocked(anyInt())).thenReturn(false)
+
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ setUpControlsListingControls(listings)
+ `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
+
+ createStartable(enabled = true).onBootCompleted()
+ fakeExecutor.runAllReady()
+
+ val intentFilterCaptor = argumentCaptor<IntentFilter>()
+ val receiverCaptor = argumentCaptor<BroadcastReceiver>()
+
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ capture(receiverCaptor),
+ capture(intentFilterCaptor),
+ eq(fakeExecutor),
+ nullable(),
+ anyInt(),
+ nullable()
+ )
+ assertThat(intentFilterCaptor.value.matchAction(Intent.ACTION_USER_UNLOCKED)).isTrue()
+
+ // User is unlocked
+ whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true)
+ receiverCaptor.value.onReceive(mock(), Intent(Intent.ACTION_USER_UNLOCKED))
+
+ verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
+ }
+
+ @Test
fun testPreferredSelectionPanel_listingNoPanel_notBind() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
setUpControlsListingControls(listings)
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController, never()).bindComponentForPanel(any())
@@ -213,7 +286,7 @@
setUpControlsListingControls(listings)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
fakeExecutor.runAllReady()
verify(controlsController, never()).bindComponentForPanel(any())
@@ -228,7 +301,7 @@
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
- createStartable(enabled = true).start()
+ createStartable(enabled = true).onBootCompleted()
verify(controlsController, never()).setPreferredSelection(any())
}
@@ -258,6 +331,8 @@
userTracker,
authorizedPanelsRepository,
preferredPanelsRepository,
+ userManager,
+ broadcastDispatcher,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 2ab1b99..9cd2220 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -125,4 +125,26 @@
assertThat(sideFpsIsShowing).isEqualTo(true)
job.cancel()
}
+
+ @Test
+ fun isShowing() = runTest {
+ var isShowing: Boolean? = null
+ val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
+ repository.setPrimaryShow(true)
+ // Run the tasks that are pending at this point of virtual time.
+ runCurrent()
+ assertThat(isShowing).isEqualTo(true)
+ job.cancel()
+ }
+
+ @Test
+ fun isNotShowing() = runTest {
+ var isShowing: Boolean? = null
+ val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
+ repository.setPrimaryShow(false)
+ // Run the tasks that are pending at this point of virtual time.
+ runCurrent()
+ assertThat(isShowing).isEqualTo(false)
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index feb429d..eb78ded 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -389,6 +389,15 @@
}
@Test
+ fun getGuidedTransformationTranslationY_previousHostInvisible_returnsZero() {
+ goToLockscreen()
+ enterGuidedTransformation()
+ whenever(lockHost.visible).thenReturn(false)
+
+ assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()).isEqualTo(0)
+ }
+
+ @Test
fun isCurrentlyInGuidedTransformation_hostsVisible_returnsTrue() {
goToLockscreen()
enterGuidedTransformation()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
index 0e7e039..4989a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
@@ -18,6 +18,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
@@ -27,23 +28,27 @@
@SmallTest
class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
- private val progressProvider = RemoteUnfoldTransitionReceiver { it.run() }
+ private val progressProvider =
+ RemoteUnfoldTransitionReceiver(useReceivingFilter = true) { runOnMainSync(it) }
+ private val progressProviderWithoutFilter =
+ RemoteUnfoldTransitionReceiver(useReceivingFilter = false) { it.run() }
private val listener = TestUnfoldProgressListener()
@Before
fun setUp() {
progressProvider.addCallback(listener)
+ progressProviderWithoutFilter.addCallback(listener)
}
@Test
- fun onTransitionStarted_propagated() {
+ fun onTransitionStarted_withFilter_propagated() {
progressProvider.onTransitionStarted()
listener.assertStarted()
}
@Test
- fun onTransitionProgress_propagated() {
+ fun onTransitionProgress_withFilter_propagated() {
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0.5f)
@@ -52,7 +57,7 @@
}
@Test
- fun onTransitionEnded_propagated() {
+ fun onTransitionEnded_withFilter_propagated() {
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0.5f)
@@ -62,11 +67,52 @@
}
@Test
- fun onTransitionStarted_afterCallbackRemoved_notPropagated() {
+ fun onTransitionStarted_withFilter_afterCallbackRemoved_notPropagated() {
progressProvider.removeCallback(listener)
progressProvider.onTransitionStarted()
listener.assertNotStarted()
}
+
+ @Test
+ fun onTransitionStarted_withoutFilter_propagated() {
+ progressProviderWithoutFilter.onTransitionStarted()
+
+ listener.assertStarted()
+ }
+
+ @Test
+ fun onTransitionProgress_withoutFilter_propagated() {
+ progressProviderWithoutFilter.onTransitionStarted()
+
+ progressProviderWithoutFilter.onTransitionProgress(0.5f)
+
+ listener.assertLastProgress(0.5f)
+ }
+
+ @Test
+ fun onTransitionEnded_withoutFilter_propagated() {
+ progressProviderWithoutFilter.onTransitionStarted()
+ progressProviderWithoutFilter.onTransitionProgress(0.5f)
+
+ progressProviderWithoutFilter.onTransitionFinished()
+
+ listener.ensureTransitionFinished()
+ }
+
+ @Test
+ fun onTransitionStarted_withoutFilter_afterCallbackRemoved_notPropagated() {
+ progressProviderWithoutFilter.removeCallback(listener)
+
+ progressProviderWithoutFilter.onTransitionStarted()
+
+ listener.assertNotStarted()
+ }
+
+ private fun runOnMainSync(f: Runnable) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync { f.run() }
+ // Sleep as the animator used from the filter has a callback that happens at every frame.
+ Thread.sleep(60)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index f653207..1ce2572 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -126,7 +126,7 @@
}
fun assertLastProgress(progress: Float) {
- assertThat(progressHistory.last()).isEqualTo(progress)
+ assertThat(progressHistory.last()).isWithin(1.0E-4F).of(progress)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt
new file mode 100644
index 0000000..f14009aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/UnfoldRemoteFilterTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.progress
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.SysuiTestCase
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldRemoteFilterTest : SysuiTestCase() {
+ private val listener = TestUnfoldProgressListener()
+
+ private val progressProvider = UnfoldRemoteFilter(listener)
+
+ @Test
+ fun onTransitionStarted_propagated() {
+ runOnMainThreadWithInterval({ progressProvider.onTransitionStarted() })
+ listener.assertStarted()
+ }
+
+ @Test
+ fun onTransitionProgress_withInterval_propagated() {
+ runOnMainThreadWithInterval(
+ { progressProvider.onTransitionStarted() },
+ { progressProvider.onTransitionProgress(0.5f) }
+ )
+
+ listener.assertLastProgress(0.5f)
+ }
+
+ @Test
+ fun onTransitionEnded_propagated() {
+ runOnMainThreadWithInterval(
+ { progressProvider.onTransitionStarted() },
+ { progressProvider.onTransitionProgress(0.5f) },
+ { progressProvider.onTransitionFinished() },
+ )
+
+ listener.ensureTransitionFinished()
+ }
+
+ private fun runOnMainThreadWithInterval(
+ vararg blocks: () -> Unit,
+ interval: Duration = 60.milliseconds
+ ) {
+ blocks.forEach {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync { it() }
+ Thread.sleep(interval.inWholeMilliseconds)
+ }
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
index b395d9c..a639df5 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.unfold
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UseReceivingFilter
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
import dagger.Module
@@ -42,4 +43,6 @@
remoteReceiver.addCallback(traceListener)
return Optional.of(remoteReceiver)
}
+
+ @Provides @UseReceivingFilter fun useReceivingFilter(): Boolean = true
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UseReceivingFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UseReceivingFilter.kt
new file mode 100644
index 0000000..60e9307
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UseReceivingFilter.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.dagger
+
+import javax.inject.Qualifier
+
+/** Annotates whether to use a filter in [RemoteUnfoldTransitionReceiver]. */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UseReceivingFilter
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt
index 5e4bcc9..b2c26c4 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt
@@ -16,9 +16,13 @@
package com.android.systemui.unfold.progress
+import android.util.Log
+import androidx.annotation.BinderThread
+import androidx.annotation.FloatRange
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.dagger.UseReceivingFilter
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -30,21 +34,40 @@
*/
class RemoteUnfoldTransitionReceiver
@Inject
-constructor(@UnfoldMain private val executor: Executor) :
- UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
+constructor(
+ @UseReceivingFilter useReceivingFilter: Boolean,
+ @UnfoldMain private val executor: Executor
+) : UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
+ private val outputProgressListener = ProcessedProgressListener()
+ private val filter: TransitionProgressListener? =
+ if (useReceivingFilter) {
+ UnfoldRemoteFilter(outputProgressListener)
+ } else {
+ null
+ }
+ @BinderThread
override fun onTransitionStarted() {
- executor.execute { listeners.forEach { it.onTransitionStarted() } }
+ executor.execute {
+ filter?.onTransitionStarted() ?: outputProgressListener.onTransitionStarted()
+ }
}
+ @BinderThread
override fun onTransitionProgress(progress: Float) {
- executor.execute { listeners.forEach { it.onTransitionProgress(progress) } }
+ executor.execute {
+ filter?.onTransitionProgress(progress)
+ ?: outputProgressListener.onTransitionProgress(progress)
+ }
}
+ @BinderThread
override fun onTransitionFinished() {
- executor.execute { listeners.forEach { it.onTransitionFinished() } }
+ executor.execute {
+ filter?.onTransitionFinished() ?: outputProgressListener.onTransitionFinished()
+ }
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -58,4 +81,30 @@
override fun destroy() {
listeners.clear()
}
+
+ private inner class ProcessedProgressListener : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ log { "onTransitionStarted" }
+ listeners.forEach { it.onTransitionStarted() }
+ }
+
+ override fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {
+ log { "onTransitionProgress" }
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+
+ override fun onTransitionFinished() {
+ log { "onTransitionFinished" }
+ listeners.forEach { it.onTransitionFinished() }
+ }
+ }
+
+ private fun log(s: () -> String) {
+ if (DEBUG) {
+ Log.d(TAG, s())
+ }
+ }
}
+
+private const val TAG = "RemoteUnfoldReceiver"
+private val DEBUG = false
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
new file mode 100644
index 0000000..0b019d1
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
@@ -0,0 +1,85 @@
+package com.android.systemui.unfold.progress
+
+import android.os.Trace
+import android.util.Log
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+
+/**
+ * Makes progress received from other processes resilient to jank.
+ *
+ * Sender and receiver processes might have different frame-rates. If the sending process is
+ * dropping a frame due to jank (or generally because it's main thread is too busy), we don't want
+ * the receiving process to drop progress frames as well. For this reason, a spring animator pass
+ * (with very high stiffness) is applied to the incoming progress. This adds a small delay to the
+ * progress (~30ms), but guarantees an always smooth animation on the receiving end.
+ */
+class UnfoldRemoteFilter(
+ private val listener: UnfoldTransitionProgressProvider.TransitionProgressListener
+) : UnfoldTransitionProgressProvider.TransitionProgressListener {
+
+ private val springAnimation =
+ SpringAnimation(this, AnimationProgressProperty).apply {
+ spring =
+ SpringForce().apply {
+ dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ stiffness = 100_000f
+ finalPosition = 1.0f
+ }
+ setMinValue(0f)
+ setMaxValue(1f)
+ minimumVisibleChange = 0.001f
+ }
+
+ private var inProgress = false
+
+ private var processedProgress: Float = 0.0f
+ set(newProgress) {
+ if (inProgress) {
+ logCounter({ "$TAG#filtered_progress" }, newProgress)
+ listener.onTransitionProgress(newProgress)
+ } else {
+ Log.e(TAG, "Filtered progress received received while animation not in progress.")
+ }
+ field = newProgress
+ }
+
+ override fun onTransitionStarted() {
+ listener.onTransitionStarted()
+ inProgress = true
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ logCounter({ "$TAG#plain_remote_progress" }, progress)
+ if (inProgress) {
+ springAnimation.animateToFinalPosition(progress)
+ } else {
+ Log.e(TAG, "Progress received while not in progress.")
+ }
+ }
+
+ override fun onTransitionFinished() {
+ inProgress = false
+ listener.onTransitionFinished()
+ }
+
+ private object AnimationProgressProperty :
+ FloatPropertyCompat<UnfoldRemoteFilter>("UnfoldRemoteFilter") {
+
+ override fun setValue(provider: UnfoldRemoteFilter, value: Float) {
+ provider.processedProgress = value
+ }
+
+ override fun getValue(provider: UnfoldRemoteFilter): Float = provider.processedProgress
+ }
+ private fun logCounter(name: () -> String, progress: Float) {
+ if (DEBUG) {
+ Trace.setCounter(name(), (progress * 100).toLong())
+ }
+ }
+}
+
+private val TAG = "UnfoldRemoteFilter"
+private val DEBUG = false
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index a9e7d4b..c9cdba9 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -106,12 +106,12 @@
};
// Spatializer state machine
- private static final int STATE_UNINITIALIZED = 0;
- private static final int STATE_NOT_SUPPORTED = 1;
- private static final int STATE_DISABLED_UNAVAILABLE = 3;
- private static final int STATE_ENABLED_UNAVAILABLE = 4;
- private static final int STATE_ENABLED_AVAILABLE = 5;
- private static final int STATE_DISABLED_AVAILABLE = 6;
+ /*package*/ static final int STATE_UNINITIALIZED = 0;
+ /*package*/ static final int STATE_NOT_SUPPORTED = 1;
+ /*package*/ static final int STATE_DISABLED_UNAVAILABLE = 3;
+ /*package*/ static final int STATE_ENABLED_UNAVAILABLE = 4;
+ /*package*/ static final int STATE_ENABLED_AVAILABLE = 5;
+ /*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
private int mState = STATE_UNINITIALIZED;
private boolean mFeatureEnabled = false;
@@ -147,9 +147,9 @@
.setSampleRate(48000)
.setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
.build();
- // device array to store the routing for the default attributes and format, size 1 because
- // media is never expected to be duplicated
- private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1];
+ // device array to store the routing for the default attributes and format, initialized to
+ // an empty list as routing hasn't been established yet
+ private static ArrayList<AudioDeviceAttributes> sRoutingDevices = new ArrayList<>(0);
//---------------------------------------------------------------
// audio device compatibility / enabled
@@ -184,11 +184,6 @@
SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
}
- synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
- mBinauralSupported = hasBinaural;
- mTransauralSupported = hasTransaural;
- }
-
synchronized void init(boolean effectExpected, @Nullable String settings) {
loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) {
@@ -322,8 +317,7 @@
return;
}
mState = STATE_DISABLED_UNAVAILABLE;
- mASA.getDevicesForAttributes(
- DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
+ sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
// note at this point mSpat is still not instantiated
}
@@ -365,34 +359,35 @@
case STATE_DISABLED_AVAILABLE:
break;
}
- mASA.getDevicesForAttributes(
- DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
+
+ sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
// check validity of routing information
- if (ROUTING_DEVICES[0] == null) {
- logloge("onRoutingUpdated: device is null, no Spatial Audio");
+ if (sRoutingDevices.isEmpty()) {
+ logloge("onRoutingUpdated: no device, no Spatial Audio");
setDispatchAvailableState(false);
// not changing the spatializer level as this is likely a transient state
return;
}
+ final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
// is media routed to a new device?
- if (isWireless(ROUTING_DEVICES[0].getType())) {
- addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
+ if (isWireless(currentDevice.getType())) {
+ addWirelessDeviceIfNew(currentDevice);
}
// find if media device enabled / available
- final Pair<Boolean, Boolean> enabledAvailable = evaluateState(ROUTING_DEVICES[0]);
+ final Pair<Boolean, Boolean> enabledAvailable = evaluateState(currentDevice);
boolean able = false;
if (enabledAvailable.second) {
// available for Spatial audio, check w/ effect
- able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
+ able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, sRoutingDevices);
loglogi("onRoutingUpdated: can spatialize media 5.1:" + able
- + " on device:" + ROUTING_DEVICES[0]);
+ + " on device:" + currentDevice);
setDispatchAvailableState(able);
} else {
- loglogi("onRoutingUpdated: device:" + ROUTING_DEVICES[0]
+ loglogi("onRoutingUpdated: device:" + currentDevice
+ " not available for Spatial Audio");
setDispatchAvailableState(false);
}
@@ -400,10 +395,10 @@
boolean enabled = able && enabledAvailable.first;
if (enabled) {
loglogi("Enabling Spatial Audio since enabled for media device:"
- + ROUTING_DEVICES[0]);
+ + currentDevice);
} else {
loglogi("Disabling Spatial Audio since disabled for media device:"
- + ROUTING_DEVICES[0]);
+ + currentDevice);
}
if (mSpat != null) {
byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL
@@ -736,9 +731,13 @@
}
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
- @NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
- if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
- return AudioSystem.canBeSpatialized(attributes, format, devices);
+ @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ return false;
+ }
+ if (isDeviceCompatibleWithSpatializationModes(devices.get(0))) {
+ AudioDeviceAttributes[] devArray = new AudioDeviceAttributes[devices.size()];
+ return AudioSystem.canBeSpatialized(attributes, format, devices.toArray(devArray));
}
return false;
}
@@ -1014,10 +1013,13 @@
logd("canBeSpatialized false due to usage:" + attributes.getUsage());
return false;
}
- AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+
// going through adapter to take advantage of routing cache
- mASA.getDevicesForAttributes(
- attributes, false /* forVolume */).toArray(devices);
+ final ArrayList<AudioDeviceAttributes> devices = getRoutingDevices(attributes);
+ if (devices.isEmpty()) {
+ logloge("canBeSpatialized got no device for " + attributes);
+ return false;
+ }
final boolean able = canBeSpatializedOnDevice(attributes, format, devices);
logd("canBeSpatialized usage:" + attributes.getUsage()
+ " format:" + format.toLogFriendlyString() + " returning " + able);
@@ -1148,8 +1150,13 @@
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
- && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
+ if (sRoutingDevices.isEmpty()) {
+ logloge("setHeadTrackerEnabled: no device, bailing");
+ return;
+ }
+ final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
+ if (currentDevice.getType() == ada.getType()
+ && currentDevice.getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
if (enabled && !mHeadTrackerAvailable) {
@@ -1706,10 +1713,11 @@
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
- final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
- if (currentDevice == null) {
+ if (sRoutingDevices.isEmpty()) {
+ logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
return headHandle;
}
+ final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
@@ -1743,6 +1751,23 @@
return screenHandle;
}
+ /**
+ * Returns routing for the given attributes
+ * @param aa AudioAttributes whose routing is being queried
+ * @return a non-null never-empty list of devices. If the routing query failed, the list
+ * will contain null.
+ */
+ private @NonNull ArrayList<AudioDeviceAttributes> getRoutingDevices(AudioAttributes aa) {
+ final ArrayList<AudioDeviceAttributes> devices = mASA.getDevicesForAttributes(
+ aa, false /* forVolume */);
+ for (AudioDeviceAttributes ada : devices) {
+ if (ada == null) {
+ // invalid entry, reject this routing query by returning an empty list
+ return new ArrayList<>(0);
+ }
+ }
+ return devices;
+ }
private static void loglogi(String msg) {
AudioService.sSpatialLogger.loglogi(msg, TAG);
@@ -1759,4 +1784,13 @@
/*package*/ void clearSADevices() {
mSADevices.clear();
}
+
+ /*package*/ synchronized void forceStateForTest(int state) {
+ mState = state;
+ }
+
+ /*package*/ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
+ mBinauralSupported = hasBinaural;
+ mTransauralSupported = hasTransaural;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a8626df..acadf4a0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -100,6 +100,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
@@ -827,6 +828,13 @@
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
+ /**
+ * Whether the application has desk mode resources. Calculated and cached when
+ * {@link #hasDeskResources()} is called.
+ */
+ @Nullable
+ private Boolean mHasDeskResources;
+
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -9490,7 +9498,14 @@
configChanged |= CONFIG_UI_MODE;
}
- return (changes&(~configChanged)) != 0;
+ // TODO(b/274944389): remove workaround after long-term solution is implemented
+ // Don't restart due to desk mode change if the app does not have desk resources.
+ if (mWmService.mSkipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(changesConfig)
+ && !hasDeskResources()) {
+ configChanged |= CONFIG_UI_MODE;
+ }
+
+ return (changes & (~configChanged)) != 0;
}
/**
@@ -9503,6 +9518,50 @@
!= isInVrUiMode(lastReportedConfig));
}
+ /**
+ * Returns true if the uiMode configuration changed, and desk mode
+ * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
+ */
+ private boolean onlyDeskInUiModeChanged(Configuration lastReportedConfig) {
+ final Configuration currentConfig = getConfiguration();
+
+ boolean deskModeChanged = isInDeskUiMode(currentConfig) != isInDeskUiMode(
+ lastReportedConfig);
+ // UI mode contains fields other than the UI mode type, so determine if any other fields
+ // changed.
+ boolean uiModeOtherFieldsChanged =
+ (currentConfig.uiMode & ~UI_MODE_TYPE_MASK) != (lastReportedConfig.uiMode
+ & ~UI_MODE_TYPE_MASK);
+
+ return deskModeChanged && !uiModeOtherFieldsChanged;
+ }
+
+ /**
+ * Determines whether or not the application has desk mode resources.
+ */
+ boolean hasDeskResources() {
+ if (mHasDeskResources != null) {
+ // We already determined this, return the cached value.
+ return mHasDeskResources;
+ }
+
+ mHasDeskResources = false;
+ try {
+ Resources packageResources = mAtmService.mContext.createPackageContextAsUser(
+ packageName, 0, UserHandle.of(mUserId)).getResources();
+ for (Configuration sizeConfiguration :
+ packageResources.getSizeAndUiModeConfigurations()) {
+ if (isInDeskUiMode(sizeConfiguration)) {
+ mHasDeskResources = true;
+ break;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Exception thrown during checking for desk resources " + this, e);
+ }
+ return mHasDeskResources;
+ }
+
private int getConfigurationChanges(Configuration lastReportedConfig) {
// Determine what has changed. May be nothing, if this is a config that has come back from
// the app after going idle. In that case we just want to leave the official config object
@@ -9798,6 +9857,10 @@
return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
}
+ private static boolean isInDeskUiMode(Configuration config) {
+ return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
+ }
+
String getProcessName() {
return info.applicationInfo.processName;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 41eb2c9..fb72d6c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -378,10 +378,7 @@
// Checking whether an activity in fullscreen rather than the task as this camera
// compat treatment doesn't cover activity embedding.
if (topActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- if (topActivity.mLetterboxUiController
- .isOverrideOrientationOnlyForCameraEnabled()) {
- topActivity.recomputeConfiguration();
- }
+ topActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
mDisplayContent.updateOrientation();
return;
}
@@ -447,9 +444,7 @@
|| topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return;
}
- if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
- topActivity.recomputeConfiguration();
- }
+ topActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
mDisplayContent.updateOrientation();
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index ec04894..6936a2d 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -53,6 +53,9 @@
*/
static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f;
+ /** Letterboxed app window position multiplier indicating center position. */
+ static final float LETTERBOX_POSITION_MULTIPLIER_CENTER = 0.5f;
+
/** Enum for Letterbox background type. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
@@ -212,6 +215,10 @@
// otherwise the apps get blacked out when they are resumed and do not have focus yet.
private boolean mIsCompatFakeFocusEnabled;
+ // Whether should use split screen aspect ratio for the activity when camera compat treatment
+ // is enabled and activity is connected to the camera in fullscreen.
+ private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
+
// Whether camera compatibility treatment is enabled.
// See DisplayRotationCompatPolicy for context.
private final boolean mIsCameraCompatTreatmentEnabled;
@@ -300,6 +307,8 @@
R.bool.config_letterboxIsEnabledForTranslucentActivities);
mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
+ mIsCameraCompatSplitScreenAspectRatioEnabled = mContext.getResources().getBoolean(
+ R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean(
R.bool.config_isCompatFakeFocusEnabled);
mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
@@ -1122,6 +1131,14 @@
return mIsPolicyForIgnoringRequestedOrientationEnabled;
}
+ /**
+ * Whether should use split screen aspect ratio for the activity when camera compat treatment
+ * is enabled and activity is connected to the camera in fullscreen.
+ */
+ boolean isCameraCompatSplitScreenAspectRatioEnabled() {
+ return mIsCameraCompatSplitScreenAspectRatioEnabled;
+ }
+
/** Whether camera compatibility treatment is enabled. */
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
return mIsCameraCompatTreatmentEnabled && (!checkDeviceConfig
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 4d0c768..da4d3cb 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -77,6 +77,7 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
@@ -242,6 +243,8 @@
private boolean mIsRelauchingAfterRequestedOrientationChanged;
+ private boolean mDoubleTapEvent;
+
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
@@ -398,13 +401,7 @@
+ mActivityRecord);
return true;
}
- DisplayContent displayContent = mActivityRecord.mDisplayContent;
- if (displayContent == null) {
- return false;
- }
- if (displayContent.mDisplayRotationCompatPolicy != null
- && displayContent.mDisplayRotationCompatPolicy
- .isTreatmentEnabledForActivity(mActivityRecord)) {
+ if (isCameraCompatTreatmentActive()) {
Slog.w(TAG, "Ignoring orientation update to "
+ screenOrientationToString(requestedOrientation)
+ " due to camera compat treatment for " + mActivityRecord);
@@ -642,6 +639,16 @@
mBooleanPropertyCameraCompatAllowForceRotation);
}
+ private boolean isCameraCompatTreatmentActive() {
+ DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ if (displayContent == null) {
+ return false;
+ }
+ return displayContent.mDisplayRotationCompatPolicy != null
+ && displayContent.mDisplayRotationCompatPolicy
+ .isTreatmentEnabledForActivity(mActivityRecord);
+ }
+
private boolean isCompatChangeEnabled(long overrideChangeId) {
return mActivityRecord.info.isChangeEnabled(overrideChangeId);
}
@@ -834,6 +841,12 @@
}
}
+ boolean isFromDoubleTap() {
+ final boolean isFromDoubleTap = mDoubleTapEvent;
+ mDoubleTapEvent = false;
+ return isFromDoubleTap;
+ }
+
SurfaceControl getLetterboxParentSurface() {
if (mActivityRecord.isInLetterboxAnimation()) {
return mActivityRecord.getTask().getSurfaceControl();
@@ -904,13 +917,42 @@
}
float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
- // Don't resize to split screen size when half folded if letterbox position is centered
- return isDisplayFullScreenAndSeparatingHinge()
- && getHorizontalPositionMultiplier(parentConfiguration) != 0.5f
- ? getSplitScreenAspectRatio()
- : mActivityRecord.shouldCreateCompatDisplayInsets()
- ? getDefaultMinAspectRatioForUnresizableApps()
- : getDefaultMinAspectRatio();
+ return shouldUseSplitScreenAspectRatio(parentConfiguration)
+ ? getSplitScreenAspectRatio()
+ : mActivityRecord.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps()
+ : getDefaultMinAspectRatio();
+ }
+
+ void recomputeConfigurationForCameraCompatIfNeeded() {
+ if (isOverrideOrientationOnlyForCameraEnabled()
+ || isCameraCompatSplitScreenAspectRatioAllowed()) {
+ mActivityRecord.recomputeConfiguration();
+ }
+ }
+
+ /**
+ * Whether we use split screen aspect ratio for the activity when camera compat treatment
+ * is active because the corresponding config is enabled and activity supports resizing.
+ */
+ private boolean isCameraCompatSplitScreenAspectRatioAllowed() {
+ return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
+ && !mActivityRecord.shouldCreateCompatDisplayInsets();
+ }
+
+ private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
+ final boolean isBookMode = isDisplayFullScreenAndInPosture(
+ DeviceStateController.DeviceState.HALF_FOLDED,
+ /* isTabletop */ false);
+ final boolean isNotCenteredHorizontally = getHorizontalPositionMultiplier(
+ parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
+ final boolean isTabletopMode = isDisplayFullScreenAndInPosture(
+ DeviceStateController.DeviceState.HALF_FOLDED,
+ /* isTabletop */ true);
+ // Don't resize to split screen size when in book mode if letterbox position is centered
+ return ((isBookMode && isNotCenteredHorizontally) || isTabletopMode)
+ || isCameraCompatSplitScreenAspectRatioAllowed()
+ && isCameraCompatTreatmentActive();
}
private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -970,7 +1012,7 @@
@LetterboxConfiguration.LetterboxHorizontalReachabilityPosition
int getLetterboxPositionForHorizontalReachability() {
- final boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge();
+ final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled();
return mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(
isInFullScreenBookMode);
}
@@ -1011,7 +1053,7 @@
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
logLetterboxPositionChange(changeToLog);
}
-
+ mDoubleTapEvent = true;
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
}
@@ -1050,7 +1092,7 @@
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
logLetterboxPositionChange(changeToLog);
}
-
+ mDoubleTapEvent = true;
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index aed876d..f5c44d9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3485,6 +3485,7 @@
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap();
if (info.isLetterboxDoubleTapEnabled) {
info.topActivityLetterboxWidth = top.getBounds().width();
info.topActivityLetterboxHeight = top.getBounds().height();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ccce558..39165dd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -552,6 +552,16 @@
// everything else on screen). Otherwise, it will be put under always-on-top stacks.
final boolean mAssistantOnTopOfDream;
+ /**
+ * If true, don't relaunch the activity upon receiving a configuration change to transition to
+ * or from the {@link UI_MODE_TYPE_DESK} uiMode, which is sent when docking. The configuration
+ * change will still be sent regardless, only the relaunch is skipped. Apps with desk resources
+ * are exempt from this and will behave like normal, since they may expect the relaunch upon the
+ * desk uiMode change.
+ */
+ @VisibleForTesting
+ boolean mSkipActivityRelaunchWhenDocking;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1226,6 +1236,8 @@
com.android.internal.R.bool.config_perDisplayFocusEnabled);
mAssistantOnTopOfDream = context.getResources().getBoolean(
com.android.internal.R.bool.config_assistantOnTopOfDream);
+ mSkipActivityRelaunchWhenDocking = context.getResources()
+ .getBoolean(R.bool.config_skipActivityRelaunchWhenDocking);
mLetterboxConfiguration = new LetterboxConfiguration(
// Using SysUI context to have access to Material colors extracted from Wallpaper.
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 dea31d7..3ad24de 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -17,12 +17,17 @@
import com.android.server.audio.SpatializerHelper.SADeviceState;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
import android.media.AudioSystem;
import android.util.Log;
@@ -36,6 +41,7 @@
import org.mockito.Mock;
import org.mockito.Spy;
+import java.util.ArrayList;
import java.util.List;
@MediumTest
@@ -49,16 +55,35 @@
@Mock private AudioService mMockAudioService;
@Spy private AudioSystemAdapter mSpyAudioSystem;
+ @Mock private AudioSystemAdapter mMockAudioSystem;
@Before
public void setUp() throws Exception {
mMockAudioService = mock(AudioService.class);
- mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+ }
- mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
+ /**
+ * Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy
+ * AudioSystemAdapter
+ * @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use
+ * the mock adapter, mMockAudioSystem.
+ */
+ private void setUpSpatHelper(boolean useSpyAudioSystem) {
+ final AudioSystemAdapter asAdapter;
+ if (useSpyAudioSystem) {
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+ asAdapter = mSpyAudioSystem;
+ mMockAudioSystem = null;
+ } else {
+ mSpyAudioSystem = null;
+ mMockAudioSystem = mock(NoOpAudioSystemAdapter.class);
+ asAdapter = mMockAudioSystem;
+ }
+ mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
true /*binauralEnabledDefault*/,
true /*transauralEnabledDefault*/,
false /*headTrackingEnabledDefault*/);
+
}
/**
@@ -68,6 +93,7 @@
*/
@Test
public void testSADeviceStateNullAddressCtor() throws Exception {
+ setUpSpatHelper(true /*useSpyAudioSystem*/);
try {
SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
@@ -78,6 +104,7 @@
@Test
public void testSADeviceStateStringSerialization() throws Exception {
Log.i(TAG, "starting testSADeviceStateStringSerialization");
+ setUpSpatHelper(true /*useSpyAudioSystem*/);
final SADeviceState devState = new SADeviceState(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
devState.mHasHeadTracker = false;
@@ -93,6 +120,7 @@
@Test
public void testSADeviceSettings() throws Exception {
Log.i(TAG, "starting testSADeviceSettings");
+ setUpSpatHelper(true /*useSpyAudioSystem*/);
final AudioDeviceAttributes dev1 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
final AudioDeviceAttributes dev2 =
@@ -143,4 +171,34 @@
Log.i(TAG, "device settingsRestored: " + settingsRestored);
Assert.assertEquals(settings, settingsRestored);
}
+
+ /**
+ * Test that null devices for routing do not break canBeSpatialized
+ * @throws Exception
+ */
+ @Test
+ public void testNoRoutingCanBeSpatialized() throws Exception {
+ Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
+ setUpSpatHelper(false /*useSpyAudioSystem*/);
+ mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
+
+ final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
+ final ArrayList<AudioDeviceAttributes> listWithNull = new ArrayList<>(1);
+ listWithNull.add(null);
+ final AudioAttributes media = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA).build();
+ final AudioFormat spatialFormat = new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
+
+ when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+ .thenReturn(emptyList);
+ Assert.assertFalse("can be spatialized on empty routing",
+ mSpatHelper.canBeSpatialized(media, spatialFormat));
+
+ when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+ .thenReturn(listWithNull);
+ Assert.assertFalse("can be spatialized on null routing",
+ mSpatHelper.canBeSpatialized(media, spatialFormat));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 0300eb0..2cc752c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -45,6 +45,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -490,6 +491,62 @@
}
@Test
+ public void testDeskModeChange_doesNotRelaunch() throws RemoteException {
+ mWm.mSkipActivityRelaunchWhenDocking = true;
+
+ final ActivityRecord activity = createActivityWithTask();
+ // The activity will already be relaunching out of the gate, finish the relaunch so we can
+ // test properly.
+ activity.finishRelaunching();
+ // Clear out any calls to scheduleTransaction from launching the activity.
+ reset(mAtm.getLifecycleManager());
+
+ final Task task = activity.getTask();
+ activity.setState(RESUMED, "Testing");
+
+ // Send a desk UI mode config update.
+ final Configuration newConfig = new Configuration(task.getConfiguration());
+ newConfig.uiMode |= UI_MODE_TYPE_DESK;
+ task.onRequestedOverrideConfigurationChanged(newConfig);
+ ensureActivityConfiguration(activity);
+
+ // The activity shouldn't start relaunching since it doesn't have any desk resources.
+ assertFalse(activity.isRelaunching());
+
+ // The configuration change is still sent to the activity, even if it doesn't relaunch.
+ final ActivityConfigurationChangeItem expected =
+ ActivityConfigurationChangeItem.obtain(newConfig);
+ verify(mAtm.getLifecycleManager()).scheduleTransaction(
+ eq(activity.app.getThread()), eq(activity.token), eq(expected));
+ }
+
+ @Test
+ public void testDeskModeChange_relaunchesWithDeskResources() {
+ mWm.mSkipActivityRelaunchWhenDocking = true;
+
+ final ActivityRecord activity = createActivityWithTask();
+ // The activity will already be relaunching out of the gate, finish the relaunch so we can
+ // test properly.
+ activity.finishRelaunching();
+
+ // Activities with desk resources should get relaunched when a UI_MODE_TYPE_DESK change
+ // comes in.
+ doReturn(true).when(activity).hasDeskResources();
+
+ final Task task = activity.getTask();
+ activity.setState(RESUMED, "Testing");
+
+ // Send a desk UI mode config update.
+ final Configuration newConfig = new Configuration(task.getConfiguration());
+ newConfig.uiMode |= UI_MODE_TYPE_DESK;
+ task.onRequestedOverrideConfigurationChanged(newConfig);
+ ensureActivityConfiguration(activity);
+
+ // The activity will relaunch since it has desk resources.
+ assertTrue(activity.isRelaunching());
+ }
+
+ @Test
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 23a3d52..8f31641 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -820,6 +820,33 @@
assertTrue(mController.shouldSendFakeFocus());
}
+ @Test
+ public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
+ doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(anyBoolean());
+ doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+ .isCameraCompatSplitScreenAspectRatioEnabled();
+ doReturn(false).when(mActivity.mWmService.mLetterboxConfiguration)
+ .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
+ doReturn(1.5f).when(mActivity.mWmService.mLetterboxConfiguration)
+ .getFixedOrientationLetterboxAspectRatio();
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), 1.5f, /* delta */ 0.01);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isTreatmentEnabledForActivity(eq(mActivity));
+
+ assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), mController.getSplitScreenAspectRatio(),
+ /* delta */ 0.01);
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
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 4a3f46a..e279145 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -58,6 +58,7 @@
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -3771,6 +3772,27 @@
}
@Test
+ public void testGetFixedOrientationLetterboxAspectRatio_tabletop_centered() {
+ // Set up a display in portrait with a fixed-orientation LANDSCAPE app
+ setUpDisplaySizeWithApp(1400, 2800);
+ mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+ LETTERBOX_POSITION_MULTIPLIER_CENTER);
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(
+ 1.0f /*letterboxVerticalPositionMultiplier*/);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);
+
+ Configuration parentConfig = mActivity.getParent().getConfiguration();
+
+ float actual = mActivity.mLetterboxUiController
+ .getFixedOrientationLetterboxAspectRatio(parentConfig);
+ float expected = mActivity.mLetterboxUiController.getSplitScreenAspectRatio();
+
+ assertEquals(expected, actual, 0.01);
+ }
+
+ @Test
public void testUpdateResolvedBoundsHorizontalPosition_bookModeEnabled() {
// Set up a display in landscape with a fixed-orientation PORTRAIT app
setUpDisplaySizeWithApp(2800, 1400);