Merge "More improve IME transition during task switch" into sc-v2-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index d684e4b..ec8dce0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20454,7 +20454,7 @@
method public String getProperty(String);
method public int getRingerMode();
method @Deprecated public int getRouting(int);
- method @Nullable public android.media.Spatializer getSpatializer();
+ method @NonNull public android.media.Spatializer getSpatializer();
method public int getStreamMaxVolume(int);
method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
@@ -23854,9 +23854,13 @@
public class Spatializer {
method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat);
+ method public int getImmersiveAudioLevel();
method public boolean isAvailable();
method public boolean isEnabled();
method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff
}
public static interface Spatializer.OnSpatializerStateChangedListener {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index df46822..2ecf088 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -425,6 +425,7 @@
public class DevicePolicyManager {
method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
+ method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void clearOrganizationId();
method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 77bcef3..be702c2 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -46,7 +46,7 @@
*/
@SystemService(Context.STATUS_BAR_SERVICE)
public class StatusBarManager {
-
+ // LINT.IfChange
/** @hide */
public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND;
/** @hide */
@@ -144,6 +144,7 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
+ // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt)
/**
* Default disable flags for setup
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 123046e..b570ae6 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -562,7 +562,7 @@
}
if (returnDefault) {
Bitmap defaultWallpaper = mDefaultWallpaper;
- if (defaultWallpaper == null) {
+ if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
defaultWallpaper = getDefaultWallpaper(context, which);
synchronized (this) {
mDefaultWallpaper = defaultWallpaper;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e04ad3..12444ab 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -13740,6 +13740,23 @@
}
/**
+ * Clears organization ID set by the DPC and resets the precomputed enrollment specific ID.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void clearOrganizationId() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.clearOrganizationIdForUser(myUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates and provisions a managed profile and sets the
* {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
* owner.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b6c48a1..b1364b5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -377,6 +377,7 @@
void setOrganizationColor(in ComponentName admin, in int color);
void setOrganizationColorForUser(in int color, in int userId);
+ void clearOrganizationIdForUser(int userHandle);
int getOrganizationColor(in ComponentName admin);
int getOrganizationColorForUser(int userHandle);
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 063ba11..2e94dd1 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -143,7 +143,7 @@
public ComponentName provider;
/**
- * The default height of the widget when added to a host, in dp. The widget will get
+ * The default height of the widget when added to a host, in px. The widget will get
* at least this width, and will often be given more, depending on the host.
*
* <p>This field corresponds to the <code>android:minWidth</code> attribute in
@@ -152,7 +152,7 @@
public int minWidth;
/**
- * The default height of the widget when added to a host, in dp. The widget will get
+ * The default height of the widget when added to a host, in px. The widget will get
* at least this height, and will often be given more, depending on the host.
*
* <p>This field corresponds to the <code>android:minHeight</code> attribute in
@@ -161,7 +161,7 @@
public int minHeight;
/**
- * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+ * Minimum width (in px) which the widget can be resized to. This field has no effect if it
* is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in
@@ -170,7 +170,7 @@
public int minResizeWidth;
/**
- * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+ * Minimum height (in px) which the widget can be resized to. This field has no effect if it
* is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in
@@ -179,7 +179,7 @@
public int minResizeHeight;
/**
- * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+ * Maximum width (in px) which the widget can be resized to. This field has no effect if it is
* smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
@@ -189,7 +189,7 @@
public int maxResizeWidth;
/**
- * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+ * Maximum height (in px) which the widget can be resized to. This field has no effect if it is
* smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 0b92b93..874e3f4 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -47,6 +47,8 @@
public class AccessibilityShortcutChooserActivity extends Activity {
@ShortcutType
private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
+ private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
+ "accessibility_shortcut_menu_mode";
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
private AlertDialog mMenuDialog;
private AlertDialog mPermissionDialog;
@@ -66,14 +68,30 @@
mMenuDialog = createMenuDialog();
mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
mMenuDialog.show();
+
+ if (savedInstanceState != null) {
+ final int restoreShortcutMenuMode =
+ savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE,
+ ShortcutMenuMode.LAUNCH);
+ if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) {
+ onEditButtonClicked();
+ }
+ }
}
@Override
protected void onDestroy() {
+ mMenuDialog.setOnDismissListener(null);
mMenuDialog.dismiss();
super.onDestroy();
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode());
+ }
+
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityTarget target = mTargets.get(position);
target.onSelected();
diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
index 4ce6f60..3eb9804 100644
--- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
+++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
@@ -17,19 +17,27 @@
package com.android.internal.notification;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import com.android.internal.R;
+
+/**
+ * This class provides methods to create intents for NotificationAccessConfirmationActivity.
+ */
public final class NotificationAccessConfirmationActivityContract {
- private static final ComponentName COMPONENT_NAME = new ComponentName(
- "com.android.settings",
- "com.android.settings.notification.NotificationAccessConfirmationActivity");
public static final String EXTRA_USER_ID = "user_id";
public static final String EXTRA_COMPONENT_NAME = "component_name";
public static final String EXTRA_PACKAGE_TITLE = "package_title";
- public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) {
+ /**
+ * Creates a launcher intent for NotificationAccessConfirmationActivity.
+ */
+ public static Intent launcherIntent(Context context, int userId, ComponentName component,
+ String packageTitle) {
return new Intent()
- .setComponent(COMPONENT_NAME)
+ .setComponent(ComponentName.unflattenFromString(context.getString(
+ R.string.config_notificationAccessConfirmationActivity)))
.putExtra(EXTRA_USER_ID, userId)
.putExtra(EXTRA_COMPONENT_NAME, component)
.putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505c..cd1bbb6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -1272,6 +1273,14 @@
}
/**
+ * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED.
+ */
+ public boolean isCredentialsDisabledForUser(int userId) {
+ return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId)
+ == PASSWORD_QUALITY_MANAGED;
+ }
+
+ /**
* @see StrongAuthTracker#isTrustAllowedForUser
*/
public boolean isTrustAllowedForUser(int userId) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e53d379..63d61fc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5876,7 +5876,6 @@
android:excludeFromRecents="true"
android:documentLaunchMode="never"
android:relinquishTaskIdentity="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:process=":ui"
android:visibleToInstantApps="true">
<intent-filter>
diff --git a/core/res/res/layout/accessibility_shortcut_chooser_item.xml b/core/res/res/layout/accessibility_shortcut_chooser_item.xml
index 7cca129..4d7946b 100644
--- a/core/res/res/layout/accessibility_shortcut_chooser_item.xml
+++ b/core/res/res/layout/accessibility_shortcut_chooser_item.xml
@@ -39,15 +39,20 @@
android:layout_height="48dp"
android:scaleType="fitCenter"/>
- <TextView
- android:id="@+id/accessibility_shortcut_target_label"
+ <LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_weight="1"
- android:textSize="20sp"
- android:textColor="?attr/textColorPrimary"
- android:fontFamily="sans-serif-medium"/>
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/accessibility_shortcut_target_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="20sp"
+ android:textColor="?attr/textColorPrimary"
+ android:fontFamily="sans-serif-medium"/>
+ </LinearLayout>
<TextView
android:id="@+id/accessibility_shortcut_target_status"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d5f4cad..a48ffc7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5174,6 +5174,12 @@
<item>@array/config_secondaryBuiltInDisplayWaterfallCutout</item>
</array>
+ <!-- The component name of the activity for the companion-device-manager notification access
+ confirmation. -->
+ <string name="config_notificationAccessConfirmationActivity" translatable="false">
+ com.android.settings/com.android.settings.notification.NotificationAccessConfirmationActivity
+ </string>
+
<!-- Whether the airplane mode should be reset when device boots in non-safemode after exiting
from safemode.
This flag should be enabled only when the product does not have any UI to toggle airplane
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7fe73e5..51cc7db 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2221,6 +2221,7 @@
<java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" />
<java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
<java-symbol type="bool" name="config_autoResetAirplaneMode" />
+ <java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
<java-symbol type="layout" name="resolver_list" />
<java-symbol type="id" name="resolver_list" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0d7225b..119c9391 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1207,6 +1207,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-779535710": {
+ "message": "Transition %d: Set %s as transient-launch",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-775004869": {
"message": "Not a match: %s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 9d65d28..05ebbba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -422,14 +422,6 @@
}
}
- @Override
- public void setExpandedContentAlpha(float alpha) {
- if (mExpandedView != null) {
- mExpandedView.setAlpha(alpha);
- mExpandedView.setTaskViewAlpha(alpha);
- }
- }
-
/**
* Set visibility of bubble in the expanded state.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9dafefe..c126f32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -69,6 +69,7 @@
import android.util.SparseSetArray;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
@@ -144,7 +145,6 @@
private BubbleLogger mLogger;
private BubbleData mBubbleData;
- private View mBubbleScrim;
@Nullable private BubbleStackView mStackView;
private BubbleIconFactory mBubbleIconFactory;
private BubblePositioner mBubblePositioner;
@@ -189,6 +189,9 @@
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+ /** Saved insets, used to detect WindowInset changes. */
+ private WindowInsets mWindowInsets;
+
private boolean mInflateSynchronously;
/** True when user is in status bar unlock shade. */
@@ -629,8 +632,11 @@
mBubbleData.getOverflow().initialize(this);
mWindowManager.addView(mStackView, mWmLayoutParams);
mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
- mBubblePositioner.update();
- mStackView.onDisplaySizeChanged();
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ mBubblePositioner.update();
+ mStackView.onDisplaySizeChanged();
+ }
return windowInsets;
});
} catch (IllegalStateException e) {
@@ -1372,8 +1378,9 @@
private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mBubblePositioner.setImeVisible(imeVisible, imeHeight);
if (mStackView != null) {
- mStackView.onImeVisibilityChanged(imeVisible, imeHeight);
+ mStackView.animateForIme(imeVisible);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 7d7bfb2..a87aad4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -398,6 +398,7 @@
updatePointerView();
}
+ /** Updates the size and visuals of the pointer. **/
private void updatePointerView() {
LayoutParams lp = (LayoutParams) mPointerView.getLayoutParams();
if (mCurrentPointer == mLeftPointer || mCurrentPointer == mRightPointer) {
@@ -524,9 +525,8 @@
if (mTaskView != null) {
mTaskView.setAlpha(alpha);
}
- if (mManageButton != null && mManageButton.getVisibility() == View.VISIBLE) {
- mManageButton.setAlpha(alpha);
- }
+ mPointerView.setAlpha(alpha);
+ setAlpha(alpha);
}
/**
@@ -545,6 +545,7 @@
mIsContentVisible = visibility;
if (mTaskView != null && !mIsAlphaAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
+ mPointerView.setAlpha(visibility ? 1f : 0f);
}
}
@@ -689,7 +690,7 @@
* the bubble if showing vertically.
* @param onLeft whether the stack was on the left side of the screen when expanded.
*/
- public void setPointerPosition(float bubblePosition, boolean onLeft) {
+ public void setPointerPosition(float bubblePosition, boolean onLeft, boolean animate) {
// Pointer gets drawn in the padding
final boolean showVertically = mPositioner.showBubblesVertically();
final float paddingLeft = (showVertically && onLeft)
@@ -710,6 +711,8 @@
: pointerPosition;
// Post because we need the width of the view
post(() -> {
+ mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
+ updatePointerView();
float pointerY;
float pointerX;
if (showVertically) {
@@ -721,11 +724,13 @@
pointerY = mPointerOverlap;
pointerX = bubbleCenter - (mPointerWidth / 2f);
}
- mPointerView.setTranslationY(pointerY);
- mPointerView.setTranslationX(pointerX);
- mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
- updatePointerView();
- mPointerView.setVisibility(VISIBLE);
+ if (animate) {
+ mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
+ } else {
+ mPointerView.setTranslationY(pointerY);
+ mPointerView.setTranslationX(pointerX);
+ mPointerView.setVisibility(VISIBLE);
+ }
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 705a12a..0c3a6b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -154,10 +154,6 @@
return dotPath
}
- override fun setExpandedContentAlpha(alpha: Float) {
- expandedView?.alpha = alpha
- }
-
override fun setTaskViewVisibility(visible: Boolean) {
// Overflow does not have a TaskView.
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 306224b..127d5a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -70,13 +70,16 @@
private Context mContext;
private WindowManager mWindowManager;
- private Rect mPositionRect;
private Rect mScreenRect;
private @Surface.Rotation int mRotation = Surface.ROTATION_0;
private Insets mInsets;
+ private boolean mImeVisible;
+ private int mImeHeight;
+ private boolean mIsLargeScreen;
+
+ private Rect mPositionRect;
private int mDefaultMaxBubbles;
private int mMaxBubbles;
-
private int mBubbleSize;
private int mSpacingBetweenBubbles;
@@ -98,7 +101,6 @@
private PointF mRestingStackPosition;
private int[] mPaddings = new int[4];
- private boolean mIsLargeScreen;
private boolean mShowingInTaskbar;
private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
private int mTaskbarIconSize;
@@ -302,6 +304,17 @@
return mMaxBubbles;
}
+ /** The height for the IME if it's visible. **/
+ public int getImeHeight() {
+ return mImeVisible ? mImeHeight : 0;
+ }
+
+ /** Sets whether the IME is visible. **/
+ public void setImeVisible(boolean visible, int height) {
+ mImeVisible = visible;
+ mImeHeight = height;
+ }
+
/**
* Calculates the padding for the bubble expanded view.
*
@@ -357,7 +370,7 @@
}
/** Gets the y position of the expanded view if it was top-aligned. */
- private float getExpandedViewYTopAligned() {
+ public float getExpandedViewYTopAligned() {
final int top = getAvailableRect().top;
if (showBubblesVertically()) {
return top - mPointerWidth + mExpandedViewPadding;
@@ -366,10 +379,6 @@
}
}
- public float getExpandedBubblesY() {
- return getAvailableRect().top + mExpandedViewPadding;
- }
-
/**
* Calculate the maximum height the expanded view can be depending on where it's placed on
* the screen and the size of the elements around it (e.g. padding, pointer, manage button).
@@ -464,18 +473,21 @@
: bubblePosition + (normalizedSize / 2f) - mPointerWidth;
}
+ private int getExpandedStackSize(int numberOfBubbles) {
+ return (numberOfBubbles * mBubbleSize)
+ + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
+ }
+
/**
* Returns the position of the bubble on-screen when the stack is expanded.
*
* @param index the index of the bubble in the stack.
- * @param numberOfBubbles the total number of bubbles in the stack.
- * @param onLeftEdge whether the stack would rest on the left edge of the screen when collapsed.
- * @return the x, y position of the bubble on-screen when the stack is expanded.
+ * @param state state information about the stack to help with calculations.
+ * @return the position of the bubble on-screen when the stack is expanded.
*/
- public PointF getExpandedBubbleXY(int index, int numberOfBubbles, boolean onLeftEdge) {
+ public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles);
- final float expandedStackSize = (numberOfBubbles * mBubbleSize)
- + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
+ final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
final float centerPosition = showBubblesVertically()
? mPositionRect.centerY()
: mPositionRect.centerX();
@@ -491,17 +503,66 @@
int right = mIsLargeScreen
? mPositionRect.right - mExpandedViewLargeScreenInset + mExpandedViewPadding
: mPositionRect.right - mBubbleSize;
- x = onLeftEdge
+ x = state.onLeft
? left
: right;
} else {
y = mPositionRect.top + mExpandedViewPadding;
x = rowStart + positionInRow;
}
+
+ if (showBubblesVertically() && mImeVisible) {
+ return new PointF(x, getExpandedBubbleYForIme(index, state.numberOfBubbles));
+ }
return new PointF(x, y);
}
/**
+ * Returns the position of the bubble on-screen when the stack is expanded and the IME
+ * is showing.
+ *
+ * @param index the index of the bubble in the stack.
+ * @param numberOfBubbles the total number of bubbles in the stack.
+ * @return y position of the bubble on-screen when the stack is expanded.
+ */
+ private float getExpandedBubbleYForIme(int index, int numberOfBubbles) {
+ final float top = getAvailableRect().top + mExpandedViewPadding;
+ if (!showBubblesVertically()) {
+ // Showing horizontally: align to top
+ return top;
+ }
+
+ // Showing vertically: might need to translate the bubbles above the IME.
+ // Subtract spacing here to provide a margin between top of IME and bottom of bubble row.
+ final float bottomInset = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+ final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
+ final float centerPosition = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ final float rowBottom = centerPosition + (expandedStackSize / 2f);
+ final float rowTop = centerPosition - (expandedStackSize / 2f);
+ float rowTopForIme = rowTop;
+ if (rowBottom > bottomInset) {
+ // We overlap with IME, must shift the bubbles
+ float translationY = rowBottom - bottomInset;
+ rowTopForIme = Math.max(rowTop - translationY, top);
+ if (rowTop - translationY < top) {
+ // Even if we shift the bubbles, they will still overlap with the IME.
+ // Hide the overflow for a lil more space:
+ final float expandedStackSizeNoO = getExpandedStackSize(numberOfBubbles - 1);
+ final float centerPositionNoO = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ final float rowBottomNoO = centerPositionNoO + (expandedStackSizeNoO / 2f);
+ final float rowTopNoO = centerPositionNoO - (expandedStackSizeNoO / 2f);
+ translationY = rowBottomNoO - bottomInset;
+ rowTopForIme = rowTopNoO - translationY;
+ }
+ }
+ return rowTopForIme + (index * (mBubbleSize + mSpacingBetweenBubbles));
+ }
+
+ /**
* @return the width of the bubble flyout (message originating from the bubble).
*/
public float getMaxFlyoutSize() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 5bc6128..9d0dd3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -28,6 +28,8 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
@@ -188,6 +190,7 @@
};
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
+ private StackViewState mStackViewState = new StackViewState();
private final ValueAnimator mDismissBubbleAnimator;
@@ -246,7 +249,6 @@
private int mBubbleTouchPadding;
private int mExpandedViewPadding;
private int mCornerRadius;
- private int mImeOffset;
@Nullable private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded;
@@ -757,7 +759,6 @@
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
- mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -779,7 +780,7 @@
this::animateShadows /* onStackAnimationFinished */, mPositioner);
mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
- onBubbleAnimatedOut);
+ onBubbleAnimatedOut, this);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
@@ -890,7 +891,7 @@
// Re-draw bubble row and pointer for new orientation.
beforeExpandedViewAnimation();
updateOverflowVisibility();
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
afterExpandedViewAnimation();
} /* after */);
@@ -968,7 +969,8 @@
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
if (mExpandedBubble != null) {
- mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue());
+ mExpandedBubble.getExpandedView().setTaskViewAlpha(
+ (float) valueAnimator.getAnimatedValue());
}
});
@@ -1558,7 +1560,7 @@
} else {
bubble.cleanupViews();
}
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
updateExpandedView();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
@@ -1599,7 +1601,7 @@
.map(b -> b.getIconView()).collect(Collectors.toList());
mStackAnimationController.animateReorder(bubbleViews, reorder);
}
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
}
/**
@@ -1670,7 +1672,6 @@
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
- updatePointerPosition();
if (mIsExpanded) {
hideCurrentInputMethod();
@@ -1762,6 +1763,7 @@
* not.
*/
void hideCurrentInputMethod() {
+ mPositioner.setImeVisible(false, 0);
mBubbleController.hideCurrentInputMethod();
}
@@ -1864,7 +1866,7 @@
updateBadges(false /* setBadgeForCollapsedStack */);
mBubbleContainer.setActiveController(mExpandedAnimationController);
updateOverflowVisibility();
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
@@ -1876,8 +1878,7 @@
} else {
index = getBubbleIndex(mExpandedBubble);
}
- PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleContainer.getChildCount(),
- mStackOnLeftOrWillBe);
+ PointF p = mPositioner.getExpandedBubbleXY(index, getState());
final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
mPositioner.showBubblesVertically() ? p.y : p.x);
mExpandedViewContainer.setTranslationX(0f);
@@ -1928,7 +1929,7 @@
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
if (mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.setExpandedContentAlpha(0f);
+ mExpandedBubble.getExpandedView().setTaskViewAlpha(0f);
// We'll be starting the alpha animation after a slight delay, so set this flag early
// here.
@@ -2011,8 +2012,7 @@
index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
}
// Value the bubble is animating from (back into the stack).
- final PointF p = mPositioner.getExpandedBubbleXY(index,
- mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe);
+ final PointF p = mPositioner.getExpandedBubbleXY(index, getState());
if (mPositioner.showBubblesVertically()) {
float pivotX;
float pivotY = p.y + mBubbleSize / 2f;
@@ -2109,7 +2109,7 @@
PointF p = mPositioner.getExpandedBubbleXY(isOverflow
? mBubbleContainer.getChildCount() - 1
: mBubbleData.getBubbles().indexOf(mExpandedBubble),
- mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe);
+ getState());
mExpandedViewContainer.setAlpha(1f);
mExpandedViewContainer.setVisibility(View.VISIBLE);
@@ -2187,9 +2187,20 @@
}
}
- /** Moves the bubbles out of the way if they're going to be over the keyboard. */
- public void onImeVisibilityChanged(boolean visible, int height) {
- mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
+ /**
+ * Updates the stack based for IME changes. When collapsed it'll move the stack if it
+ * overlaps where they IME would be. When expanded it'll shift the expanded bubbles
+ * if they might overlap with the IME (this only happens for large screens).
+ */
+ public void animateForIme(boolean visible) {
+ if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
+ // This will update the animation so the bubbles move to position for the IME
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition(false /* forIme */);
+ afterExpandedViewAnimation();
+ } /* after */);
+ return;
+ }
if (!mIsExpanded && getBubbleCount() > 0) {
final float stackDestinationY =
@@ -2208,9 +2219,20 @@
FLYOUT_IME_ANIMATION_SPRING_CONFIG)
.start();
}
- } else if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
+ } else if (mPositioner.showBubblesVertically() && mIsExpanded
+ && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setImeVisible(visible);
+ List<Animator> animList = new ArrayList();
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ View child = mBubbleContainer.getChildAt(i);
+ float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
+ ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
+ animList.add(anim);
+ }
+ updatePointerPosition(true /* forIme */);
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animList);
+ set.start();
}
}
@@ -2514,7 +2536,7 @@
// Account for the IME in the touchable region so that the touchable region of the
// Bubble window doesn't obscure the IME. The touchable region affects which areas
// of the screen can be excluded by lower windows (IME is just above the embedded task)
- outRect.bottom -= (int) mStackAnimationController.getImeHeight();
+ outRect.bottom -= mPositioner.getImeHeight();
}
if (mFlyout.getVisibility() == View.VISIBLE) {
@@ -2773,13 +2795,13 @@
}
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
- mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe);
+ getState());
mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
mPositioner.showBubblesVertically() ? p.y : p.x));
mExpandedViewContainer.setTranslationX(0f);
mExpandedBubble.getExpandedView().updateView(
mExpandedViewContainer.getLocationOnScreen());
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
}
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
@@ -2850,7 +2872,13 @@
}
}
- private void updatePointerPosition() {
+ /**
+ * Updates the position of the pointer based on the expanded bubble.
+ *
+ * @param forIme whether the position is being updated due to the ime appearing, in this case
+ * the pointer is animated to the location.
+ */
+ private void updatePointerPosition(boolean forIme) {
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
return;
}
@@ -2858,13 +2886,12 @@
if (index == -1) {
return;
}
- PointF bubblePosition = mPositioner.getExpandedBubbleXY(index,
- mBubbleContainer.getChildCount(),
- mStackOnLeftOrWillBe);
- mExpandedBubble.getExpandedView().setPointerPosition(mPositioner.showBubblesVertically()
- ? bubblePosition.y
- : bubblePosition.x,
- mStackOnLeftOrWillBe);
+ PointF position = mPositioner.getExpandedBubbleXY(index, getState());
+ float bubblePosition = mPositioner.showBubblesVertically()
+ ? position.y
+ : position.x;
+ mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition,
+ mStackOnLeftOrWillBe, forIme /* animate */);
}
/**
@@ -2947,6 +2974,26 @@
return bubbles;
}
+ /** @return the current stack state. */
+ public StackViewState getState() {
+ mStackViewState.numberOfBubbles = mBubbleContainer.getChildCount();
+ mStackViewState.selectedIndex = getBubbleIndex(mExpandedBubble);
+ mStackViewState.onLeft = mStackOnLeftOrWillBe;
+ return mStackViewState;
+ }
+
+ /**
+ * Holds some commonly queried information about the stack.
+ */
+ public static class StackViewState {
+ // Number of bubbles (including the overflow itself) in the stack.
+ public int numberOfBubbles;
+ // The selected index if the stack is expanded.
+ public int selectedIndex;
+ // Whether the stack is resting on the left or right side of the screen when collapsed.
+ public boolean onLeft;
+ }
+
/**
* Representation of stack position that uses relative properties rather than absolute
* coordinates. This is used to maintain similar stack positions across configuration changes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index 38b3ba9..7e55282 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -29,12 +29,6 @@
@Nullable BubbleExpandedView getExpandedView();
/**
- * Sets the alpha of the expanded view content. This will be applied to both the expanded view
- * container itself (the manage button, etc.) as well as the TaskView within it.
- */
- void setExpandedContentAlpha(float alpha);
-
- /**
* Sets whether the contents of the bubble's TaskView should be visible.
*/
void setTaskViewVisibility(boolean visible);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index c32be98..f0f78748 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
@@ -33,6 +32,7 @@
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -124,12 +124,15 @@
private BubblePositioner mPositioner;
+ private BubbleStackView mBubbleStackView;
+
public ExpandedAnimationController(BubblePositioner positioner,
- Runnable onBubbleAnimatedOutAction) {
+ Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) {
mPositioner = positioner;
updateResources();
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
mCollapsePoint = mPositioner.getDefaultStartPosition();
+ mBubbleStackView = stackView;
}
/**
@@ -239,10 +242,7 @@
final Path path = new Path();
path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
- boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
- final PointF p = mPositioner.getExpandedBubbleXY(index,
- mLayout.getChildCount(),
- onLeft);
+ final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
if (expanding) {
// If we're expanding, first draw a line from the bubble's current position to where
// it'll end up
@@ -364,7 +364,7 @@
bubbleView.setTranslationY(y);
}
- final float expandedY = mPositioner.getExpandedBubblesY();
+ final float expandedY = mPositioner.getExpandedViewYTopAligned();
final boolean draggedOutEnough =
y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
if (draggedOutEnough != mBubbleDraggedOutEnough) {
@@ -410,8 +410,7 @@
return;
}
final int index = mLayout.indexOfChild(bubbleView);
- final PointF p = mPositioner.getExpandedBubbleXY(index, mLayout.getChildCount(),
- mPositioner.isStackOnLeft(mCollapsePoint));
+ final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
animationForChildAtIndex(index)
.position(p.x, p.y)
.withPositionStartVelocities(velX, velY)
@@ -429,16 +428,6 @@
updateBubblePositions();
}
- /**
- * Animates the bubbles to the y position. Used in response to IME showing.
- */
- public void updateYPosition(Runnable after) {
- if (mLayout == null) return;
- animationsForChildrenFromIndex(
- 0, (i, anim) -> anim.translationY(mPositioner.getExpandedBubblesY()))
- .startAll(after);
- }
-
/** Description of current animation controller state. */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ExpandedAnimationController state:");
@@ -496,36 +485,33 @@
startOrUpdatePathAnimation(false /* expanding */);
} else {
boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
- final PointF p = mPositioner.getExpandedBubbleXY(index,
- mLayout.getChildCount(),
- onLeft);
+ final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
if (mPositioner.showBubblesVertically()) {
child.setTranslationY(p.y);
} else {
child.setTranslationX(p.x);
}
- if (!mPreparingToCollapse) {
- // Only animate if we're not collapsing as that animation will handle placing the
+
+ if (mPreparingToCollapse) {
+ // Don't animate if we're collapsing, as that animation will handle placing the
// new bubble in the stacked position.
- if (mPositioner.showBubblesVertically()) {
- Rect availableRect = mPositioner.getAvailableRect();
- float fromX = onLeft
- ? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
- : availableRect.right + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
- animationForChild(child)
- .translationX(fromX, p.y)
- .start();
- } else {
- // Only animate if we're not collapsing as that animation will handle placing
- // the new bubble in the stacked position.
- float fromY = mPositioner.getExpandedBubblesY() - mBubbleSizePx
- * ANIMATE_TRANSLATION_FACTOR;
- animationForChild(child)
- .translationY(fromY, p.y)
- .start();
- }
- updateBubblePositions();
+ return;
}
+
+ if (mPositioner.showBubblesVertically()) {
+ float fromX = onLeft
+ ? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
+ : p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
+ animationForChild(child)
+ .translationX(fromX, p.y)
+ .start();
+ } else {
+ float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
+ animationForChild(child)
+ .translationY(fromY, p.y)
+ .start();
+ }
+ updateBubblePositions();
}
}
@@ -572,7 +558,6 @@
if (mAnimatingExpand || mAnimatingCollapse) {
return;
}
- boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
for (int i = 0; i < mLayout.getChildCount(); i++) {
final View bubble = mLayout.getChildAt(i);
@@ -582,7 +567,7 @@
return;
}
- final PointF p = mPositioner.getExpandedBubbleXY(i, mLayout.getChildCount(), onLeft);
+ final PointF p = mPositioner.getExpandedBubbleXY(i, mBubbleStackView.getState());
animationForChild(bubble)
.translationX(p.x)
.translationY(p.y)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 9a08190..60b6433 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -127,9 +127,6 @@
/** Whether or not the stack's start position has been set. */
private boolean mStackMovedToStartPosition = false;
- /** The height of the most recently visible IME. */
- private float mImeHeight = 0f;
-
/**
* The Y position of the stack before the IME became visible, or {@link Float#MIN_VALUE} if the
* IME is not visible or the user moved the stack since the IME became visible.
@@ -173,7 +170,7 @@
*/
private boolean mSpringToTouchOnNextMotionEvent = false;
- /** Horizontal offset of bubbles in the stack. */
+ /** Offset of bubbles in the stack (i.e. how much they overlap). */
private float mStackOffset;
/** Offset between stack y and animation y for bubble swap. */
private float mSwapAnimationOffset;
@@ -521,16 +518,6 @@
removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
}
- /** Save the current IME height so that we know where the stack bounds should be. */
- public void setImeHeight(int imeHeight) {
- mImeHeight = imeHeight;
- }
-
- /** Returns the current IME height that the stack is offset by. */
- public float getImeHeight() {
- return mImeHeight;
- }
-
/**
* Animates the stack either away from the newly visible IME, or back to its original position
* due to the IME going away.
@@ -589,11 +576,14 @@
*/
public RectF getAllowableStackPositionRegion() {
final RectF allowableRegion = new RectF(mPositioner.getAvailableRect());
+ final int imeHeight = mPositioner.getImeHeight();
+ final float bottomPadding = getBubbleCount() > 1
+ ? mBubblePaddingTop + mStackOffset
+ : mBubblePaddingTop;
allowableRegion.left -= mBubbleOffscreen;
allowableRegion.top += mBubblePaddingTop;
allowableRegion.right += mBubbleOffscreen - mBubbleSize;
- allowableRegion.bottom -= mBubblePaddingTop + mBubbleSize
- + (mImeHeight != UNSET ? mImeHeight + mBubblePaddingTop : 0f);
+ allowableRegion.bottom -= imeHeight + bottomPadding + mBubbleSize;
return allowableRegion;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 9732a88..2b9bdce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,10 +16,12 @@
package com.android.wm.shell.bubbles.animation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.content.res.Configuration;
@@ -37,6 +39,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
import org.junit.Before;
import org.junit.Ignore;
@@ -56,18 +59,22 @@
private int mStackOffset;
private PointF mExpansionPoint;
private BubblePositioner mPositioner;
+ private BubbleStackView.StackViewState mStackViewState;
@SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
+ BubbleStackView stackView = mock(BubbleStackView.class);
+ when(stackView.getState()).thenReturn(getStackViewState());
mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
new Rect(0, 0, mDisplayWidth, mDisplayHeight));
mExpandedController = new ExpandedAnimationController(mPositioner,
- mOnBubbleAnimatedOutAction);
+ mOnBubbleAnimatedOutAction,
+ stackView);
spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
@@ -78,6 +85,13 @@
mExpansionPoint = new PointF(100, 100);
}
+ public BubbleStackView.StackViewState getStackViewState() {
+ mStackViewState.numberOfBubbles = mLayout.getChildCount();
+ mStackViewState.selectedIndex = 0;
+ mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
+ return mStackViewState;
+ }
+
@Test
@Ignore
public void testExpansionAndCollapse() throws InterruptedException {
@@ -141,12 +155,10 @@
/** Check that children are in the correct positions for being expanded. */
private void testBubblesInCorrectExpandedPositions() {
- boolean onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
// Check all the visible bubbles to see if they're in the right place.
for (int i = 0; i < mLayout.getChildCount(); i++) {
PointF expectedPosition = mPositioner.getExpandedBubbleXY(i,
- mLayout.getChildCount(),
- onLeft);
+ getStackViewState());
assertEquals(expectedPosition.x,
mLayout.getChildAt(i).getTranslationX(),
2f);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5db9ddf..38f9607 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2443,17 +2443,11 @@
/**
* Return a handle to the optional platform's {@link Spatializer}
- * @return {@code null} if spatialization is not supported, the {@code Spatializer} instance
- * otherwise.
+ * @return the {@code Spatializer} instance.
+ * @see Spatializer#getImmersiveAudioLevel() to check for the level of support of the effect
+ * on the platform
*/
- public @Nullable Spatializer getSpatializer() {
- int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
- try {
- level = getService().getSpatializerImmersiveAudioLevel();
- } catch (Exception e) { /* using NONE */ }
- if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
- return null;
- }
+ public @NonNull Spatializer getSpatializer() {
return new Spatializer(this);
}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 3ed8b58..d8519b6 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -114,6 +114,7 @@
/** @hide */
@IntDef(flag = false, value = {
+ SPATIALIZER_IMMERSIVE_LEVEL_OTHER,
SPATIALIZER_IMMERSIVE_LEVEL_NONE,
SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
})
@@ -121,21 +122,42 @@
public @interface ImmersiveAudioLevel {};
/**
- * @hide
+ * Constant indicating the {@code Spatializer} on this device supports a spatialization
+ * mode that differs from the ones available at this SDK level.
+ * @see #getImmersiveAudioLevel()
+ */
+ public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1;
+
+ /**
* Constant indicating there are no spatialization capabilities supported on this device.
- * @see AudioManager#getImmersiveAudioLevel()
+ * @see #getImmersiveAudioLevel()
*/
public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
/**
- * @hide
- * Constant indicating the {@link Spatializer} on this device supports multichannel
+ * Constant indicating the {@code Spatializer} on this device supports multichannel
* spatialization.
- * @see AudioManager#getImmersiveAudioLevel()
+ * @see #getImmersiveAudioLevel()
*/
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
/**
+ * Return the level of support for the spatialization feature on this device.
+ * This level of support is independent of whether the {@code Spatializer} is currently
+ * enabled or available and will not change over time.
+ * @return the level of spatialization support
+ * @see #isEnabled()
+ * @see #isAvailable()
+ */
+ public @ImmersiveAudioLevel int getImmersiveAudioLevel() {
+ int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ try {
+ level = mAm.getService().getSpatializerImmersiveAudioLevel();
+ } catch (Exception e) { /* using NONE */ }
+ return level;
+ }
+
+ /**
* @hide
* Enables / disables the spatializer effect.
* Changing the enabled state will trigger the public
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0d18b8d..e509777 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -22,6 +22,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
}
]
},
@@ -82,6 +85,9 @@
},
{
"exclude-annotation": "android.platform.helpers.Staging"
+ },
+ {
+ "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
}
]
}
@@ -101,6 +107,9 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
}
]
}
@@ -117,5 +126,24 @@
}
]
}
+ ],
+ "large-screen-postsubmit": [
+ {
+ "name": "PlatformScenarioTests",
+ "options" : [
+ {
+ "include-filter": "android.platform.test.scenario.sysui"
+ },
+ {
+ "include-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index e5933e6..9010d51 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -36,6 +36,13 @@
* [View.setTranslationY]
*/
private val translationApplier: TranslationApplier = object : TranslationApplier {},
+ /**
+ * Allows to set custom implementation for getting
+ * view location. Could be useful if logical view bounds
+ * are different than actual bounds (e.g. view container may
+ * have larger width than width of the items in the container)
+ */
+ private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}
) : UnfoldTransitionProgressProvider.TransitionProgressListener {
private val screenSize = Point()
@@ -43,6 +50,8 @@
private val animatedViews: MutableList<AnimatedView> = arrayListOf()
+ private var lastAnimationProgress: Float = 0f
+
/**
* Updates display properties in order to calculate the initial position for the views
* Must be called before [registerViewForAnimation]
@@ -58,6 +67,19 @@
}
/**
+ * If target view positions have changed (e.g. because of layout changes) call this method
+ * to re-query view positions and update the translations
+ */
+ fun updateViewPositions() {
+ animatedViews.forEach { animatedView ->
+ animatedView.view.get()?.let {
+ animatedView.updateAnimatedView(it)
+ }
+ }
+ onTransitionProgress(lastAnimationProgress)
+ }
+
+ /**
* Registers a view to be animated, the view should be measured and layouted
* After finishing the animation it is necessary to clear
* the views using [clearRegisteredViews]
@@ -85,45 +107,30 @@
)
}
}
+ lastAnimationProgress = progress
}
- private fun createAnimatedView(view: View): AnimatedView {
- val viewCenter = getViewCenter(view)
+ private fun createAnimatedView(view: View): AnimatedView =
+ AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
+
+ private fun AnimatedView.updateAnimatedView(view: View): AnimatedView {
+ val viewCenter = Point()
+ viewCenterProvider.getViewCenter(view, viewCenter)
+
val viewCenterX = viewCenter.x
val viewCenterY = viewCenter.y
- val translationX: Float
- val translationY: Float
-
if (isVerticalFold) {
val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
- translationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
- translationY = 0f
+ startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
+ startTranslationY = 0f
} else {
val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
- translationX = 0f
- translationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
+ startTranslationX = 0f
+ startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
}
- return AnimatedView(
- view = WeakReference(view),
- startTranslationX = translationX,
- startTranslationY = translationY
- )
- }
-
- private fun getViewCenter(view: View): Point {
- val viewLocation = IntArray(2)
- view.getLocationOnScreen(viewLocation)
-
- val viewX = viewLocation[0]
- val viewY = viewLocation[1]
-
- val outPoint = Point()
- outPoint.x = viewX + view.width / 2
- outPoint.y = viewY + view.height / 2
-
- return outPoint
+ return this
}
/**
@@ -139,10 +146,29 @@
}
}
+ /**
+ * Interface that allows to use custom logic to get the center of the view
+ */
+ interface ViewCenterProvider {
+ /**
+ * Called when we need to get the center of the view
+ */
+ fun getViewCenter(view: View, outPoint: Point) {
+ val viewLocation = IntArray(2)
+ view.getLocationOnScreen(viewLocation)
+
+ val viewX = viewLocation[0]
+ val viewY = viewLocation[1]
+
+ outPoint.x = viewX + view.width / 2
+ outPoint.y = viewY + view.height / 2
+ }
+ }
+
private class AnimatedView(
val view: WeakReference<View>,
- val startTranslationX: Float,
- val startTranslationY: Float
+ var startTranslationX: Float = 0f,
+ var startTranslationY: Float = 0f
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 3167070..ecc3245 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -30,6 +30,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
@@ -46,6 +47,7 @@
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import javax.inject.Inject;
@@ -126,6 +128,8 @@
.setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
.setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
allowSystemGestureIgnoringBarVisibility())
+ .setFlag(SYSUI_STATE_SCREEN_PINNING,
+ ActivityManagerWrapper.getInstance().isScreenPinningActive())
.commitUpdate(mDisplayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9ceeb75..4e9b0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -96,6 +96,7 @@
private final UiEventLogger mUiEventLogger;
private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
+ private final FeatureFlags mFeatureFlags;
private final List<Callback> mCallbacks = new ArrayList<>();
private AutoTileManager mAutoTiles;
@@ -123,7 +124,8 @@
UiEventLogger uiEventLogger,
UserTracker userTracker,
SecureSettings secureSettings,
- CustomTileStatePersister customTileStatePersister
+ CustomTileStatePersister customTileStatePersister,
+ FeatureFlags featureFlags
) {
mIconController = iconController;
mContext = context;
@@ -145,6 +147,7 @@
mUserTracker = userTracker;
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
+ mFeatureFlags = featureFlags;
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
@@ -266,7 +269,7 @@
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
- final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
+ final List<String> tileSpecs = loadTileSpecs(mContext, newValue, mFeatureFlags);
int currentUser = mUserTracker.getUserId();
if (currentUser != mCurrentUser) {
mUserContext = mUserTracker.getUserContext();
@@ -335,7 +338,7 @@
if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
// If we didn't manage to create any tiles, set it to empty (default)
Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
- changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
+ changeTiles(currentSpecs, loadTileSpecs(mContext, "", mFeatureFlags));
} else {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
@@ -403,7 +406,7 @@
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
- final List<String> tileSpecs = loadTileSpecs(mContext, setting);
+ final List<String> tileSpecs = loadTileSpecs(mContext, setting, mFeatureFlags);
if (changeFunction.test(tileSpecs)) {
saveTilesToSettings(tileSpecs);
}
@@ -492,7 +495,8 @@
throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
}
- protected static List<String> loadTileSpecs(Context context, String tileList) {
+ protected static List<String> loadTileSpecs(
+ Context context, String tileList, FeatureFlags featureFlags) {
final Resources res = context.getResources();
if (TextUtils.isEmpty(tileList)) {
@@ -525,6 +529,21 @@
}
}
}
+ if (featureFlags.isProviderModelSettingEnabled()) {
+ if (!tiles.contains("internet")) {
+ if (tiles.contains("wifi")) {
+ // Replace the WiFi with Internet, and remove the Cell
+ tiles.set(tiles.indexOf("wifi"), "internet");
+ tiles.remove("cell");
+ } else if (tiles.contains("cell")) {
+ // Replace the Cell with Internet
+ tiles.set(tiles.indexOf("cell"), "internet");
+ }
+ } else {
+ tiles.remove("wifi");
+ tiles.remove("cell");
+ }
+ }
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 1c20a86..993bbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -63,6 +63,7 @@
private final Executor mBgExecutor;
private final Context mContext;
private final UserTracker mUserTracker;
+ private final FeatureFlags mFeatureFlags;
private TileStateListener mListener;
private boolean mFinished;
@@ -72,12 +73,14 @@
Context context,
UserTracker userTracker,
@Main Executor mainExecutor,
- @Background Executor bgExecutor
+ @Background Executor bgExecutor,
+ FeatureFlags featureFlags
) {
mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mUserTracker = userTracker;
+ mFeatureFlags = featureFlags;
}
public void setListener(TileStateListener listener) {
@@ -118,6 +121,10 @@
}
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
+ if (mFeatureFlags.isProviderModelSettingEnabled()) {
+ possibleTiles.remove("cell");
+ possibleTiles.remove("wifi");
+ }
for (String spec : possibleTiles) {
// Only add current and stock tiles that can be created from QSFactoryImpl.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
new file mode 100644
index 0000000..cf34db2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 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.statusbar
+
+import android.app.StatusBarManager.DISABLE_BACK
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_EXPAND
+import android.app.StatusBarManager.DISABLE_HOME
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_RECENT
+import android.app.StatusBarManager.DISABLE_SEARCH
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A singleton that creates concise but readable strings representing the values of the disable
+ * flags for debugging.
+ *
+ * See [CommandQueue.disable] for information about disable flags.
+ *
+ * Note that, for both lists passed in, each flag must have a distinct [DisableFlag.flagIsSetSymbol]
+ * and distinct [DisableFlag.flagNotSetSymbol] within the list. If this isn't true, the logs could
+ * be ambiguous so an [IllegalArgumentException] is thrown.
+ */
+@SysUISingleton
+class DisableFlagsLogger constructor(
+ private val disable1FlagsList: List<DisableFlag>,
+ private val disable2FlagsList: List<DisableFlag>
+) {
+
+ @Inject
+ constructor() : this(defaultDisable1FlagsList, defaultDisable2FlagsList)
+
+ init {
+ if (flagsListHasDuplicateSymbols(disable1FlagsList)) {
+ throw IllegalArgumentException("disable1 flags must have unique symbols")
+ }
+ if (flagsListHasDuplicateSymbols(disable2FlagsList)) {
+ throw IllegalArgumentException("disable2 flags must have unique symbols")
+ }
+ }
+
+ private fun flagsListHasDuplicateSymbols(list: List<DisableFlag>): Boolean {
+ val numDistinctFlagOffStatus = list.map { it.getFlagStatus(0) }.distinct().count()
+ val numDistinctFlagOnStatus = list
+ .map { it.getFlagStatus(Int.MAX_VALUE) }
+ .distinct()
+ .count()
+ return numDistinctFlagOffStatus < list.count() || numDistinctFlagOnStatus < list.count()
+ }
+
+ /**
+ * Returns a string representing the, old, new, and new-after-modification disable flag states,
+ * as well as the differences between each of the states.
+ *
+ * Example:
+ * Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (hB.iGR) | New after local modification:
+ * EnaihBcRso.qInGR (.n)
+ *
+ * A capital character signifies the flag is set and a lowercase character signifies that the
+ * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
+ *
+ * The difference between states is written between parentheses, and won't be included if there
+ * is no difference. the new-after-modification state also won't be included if there's no
+ * difference from the new state.
+ *
+ * @param old the disable state that had been previously sent.
+ * @param new the new disable state that has just been sent.
+ * @param newAfterLocalModification the new disable states after a class has locally modified
+ * them. Null if the class does not locally modify.
+ */
+ fun getDisableFlagsString(
+ old: DisableState,
+ new: DisableState,
+ newAfterLocalModification: DisableState? = null
+ ): String {
+ val builder = StringBuilder("Received new disable state. ")
+ builder.append("Old: ")
+ builder.append(getFlagsString(old))
+ builder.append(" | New: ")
+ if (old != new) {
+ builder.append(getFlagsStringWithDiff(old, new))
+ } else {
+ builder.append(getFlagsString(old))
+ }
+
+ if (newAfterLocalModification != null && new != newAfterLocalModification) {
+ builder.append(" | New after local modification: ")
+ builder.append(getFlagsStringWithDiff(new, newAfterLocalModification))
+ }
+
+ return builder.toString()
+ }
+
+ /**
+ * Returns a string representing [new] state, as well as the difference from [old] to [new]
+ * (if there is one).
+ */
+ private fun getFlagsStringWithDiff(old: DisableState, new: DisableState): String {
+ val builder = StringBuilder()
+ builder.append(getFlagsString(new))
+ builder.append(" ")
+ builder.append(getDiffString(old, new))
+ return builder.toString()
+ }
+
+ /**
+ * Returns a string representing the difference between [old] and [new], or an empty string if
+ * there is no difference.
+ *
+ * For example, if old was "abc.DE" and new was "aBC.De", the difference returned would be
+ * "(BC.e)".
+ */
+ private fun getDiffString(old: DisableState, new: DisableState): String {
+ if (old == new) {
+ return ""
+ }
+
+ val builder = StringBuilder("(")
+ disable1FlagsList.forEach {
+ val newSymbol = it.getFlagStatus(new.disable1)
+ if (it.getFlagStatus(old.disable1) != newSymbol) {
+ builder.append(newSymbol)
+ }
+ }
+ builder.append(".")
+ disable2FlagsList.forEach {
+ val newSymbol = it.getFlagStatus(new.disable2)
+ if (it.getFlagStatus(old.disable2) != newSymbol) {
+ builder.append(newSymbol)
+ }
+ }
+ builder.append(")")
+ return builder.toString()
+ }
+
+ /** Returns a string representing the disable flag states, e.g. "EnaihBcRso.qiNGR". */
+ private fun getFlagsString(state: DisableState): String {
+ val builder = StringBuilder("")
+ disable1FlagsList.forEach { builder.append(it.getFlagStatus(state.disable1)) }
+ builder.append(".")
+ disable2FlagsList.forEach { builder.append(it.getFlagStatus(state.disable2)) }
+ return builder.toString()
+ }
+
+ /** A POJO representing each disable flag. */
+ class DisableFlag(
+ private val bitMask: Int,
+ private val flagIsSetSymbol: Char,
+ private val flagNotSetSymbol: Char
+ ) {
+
+ /**
+ * Returns a character representing whether or not this flag is set in [state].
+ *
+ * A capital character signifies the flag is set and a lowercase character signifies that
+ * the flag isn't set.
+ */
+ internal fun getFlagStatus(state: Int): Char =
+ if (0 != state and this.bitMask) this.flagIsSetSymbol
+ else this.flagNotSetSymbol
+ }
+
+ /** POJO to hold [disable1] and [disable2]. */
+ data class DisableState(val disable1: Int, val disable2: Int)
+}
+
+// LINT.IfChange
+private val defaultDisable1FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
+ DisableFlagsLogger.DisableFlag(DISABLE_EXPAND, 'E', 'e'),
+ DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ICONS, 'N', 'n'),
+ DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ALERTS, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(DISABLE_SYSTEM_INFO, 'I', 'i'),
+ DisableFlagsLogger.DisableFlag(DISABLE_HOME, 'H', 'h'),
+ DisableFlagsLogger.DisableFlag(DISABLE_BACK, 'B', 'b'),
+ DisableFlagsLogger.DisableFlag(DISABLE_CLOCK, 'C', 'c'),
+ DisableFlagsLogger.DisableFlag(DISABLE_RECENT, 'R', 'r'),
+ DisableFlagsLogger.DisableFlag(DISABLE_SEARCH, 'S', 's'),
+ DisableFlagsLogger.DisableFlag(DISABLE_ONGOING_CALL_CHIP, 'O', 'o')
+)
+
+private val defaultDisable2FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
+ DisableFlagsLogger.DisableFlag(DISABLE2_QUICK_SETTINGS, 'Q', 'q'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_SYSTEM_ICONS, 'I', 'i'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_NOTIFICATION_SHADE, 'N', 'n'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_GLOBAL_ACTIONS, 'G', 'g'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_ROTATE_SUGGESTIONS, 'R', 'r')
+)
+// LINT.ThenChange(frameworks/base/core/java/android/app/StatusBarManager.java)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 51eb496..abee7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -225,19 +225,19 @@
}
}
+ // If no one is light, all icons become white.
+ if (numLightStacks == 0) {
+ mStatusBarIconController.getTransitionsController().setIconsDark(
+ false, animateChange());
+ }
+
// If all stacks are light, all icons get dark.
- if (numLightStacks == numStacks) {
+ else if (numLightStacks == numStacks) {
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
- // If no one is light, all icons become white.
- else if (numLightStacks == 0) {
- mStatusBarIconController.getTransitionsController().setIconsDark(
- false, animateChange());
- }
-
// Not the same for every stack, magic!
else {
mStatusBarIconController.setIconsDarkArea(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
deleted file mode 100644
index b36b67d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 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.statusbar.phone;
-
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.ViewController;
-
-/** Controller for {@link PhoneStatusBarView}. */
-public class PhoneStatusBarViewController extends ViewController<PhoneStatusBarView> {
-
- protected PhoneStatusBarViewController(
- PhoneStatusBarView view,
- CommandQueue commandQueue) {
- super(view);
- mView.setPanelEnabledProvider(commandQueue::panelsEnabled);
- }
-
- @Override
- protected void onViewAttached() {
- }
-
- @Override
- protected void onViewDetached() {
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
new file mode 100644
index 0000000..9799533
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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.statusbar.phone
+
+import android.graphics.Point
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.ViewController
+
+/** Controller for [PhoneStatusBarView]. */
+class PhoneStatusBarViewController(
+ view: PhoneStatusBarView,
+ commandQueue: CommandQueue,
+ statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?
+) : ViewController<PhoneStatusBarView>(view) {
+
+ override fun onViewAttached() {}
+ override fun onViewDetached() {}
+
+ init {
+ mView.setPanelEnabledProvider {
+ commandQueue.panelsEnabled()
+ }
+
+ statusBarMoveFromCenterAnimationController?.let { animationController ->
+ val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
+ val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+
+ val viewCenterProvider = StatusBarViewsCenterProvider()
+ val viewsToAnimate = arrayOf(
+ statusBarLeftSide,
+ systemIconArea
+ )
+
+ animationController.init(viewsToAnimate, viewCenterProvider)
+
+ mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
+ val widthChanged = right - left != oldRight - oldLeft
+ if (widthChanged) {
+ statusBarMoveFromCenterAnimationController.onStatusBarWidthChanged()
+ }
+ }
+ }
+ }
+
+ private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
+ override fun getViewCenter(view: View, outPoint: Point) =
+ when (view.id) {
+ R.id.status_bar_left_side -> {
+ // items aligned to the start, return start center point
+ getViewEdgeCenter(view, outPoint, isStart = true)
+ }
+ R.id.system_icon_area -> {
+ // items aligned to the end, return end center point
+ getViewEdgeCenter(view, outPoint, isStart = false)
+ }
+ else -> super.getViewCenter(view, outPoint)
+ }
+
+ /**
+ * Returns start or end (based on [isStart]) center point of the view
+ */
+ private fun getViewEdgeCenter(view: View, outPoint: Point, isStart: Boolean) {
+ val isRtl = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ val isLeftEdge = isRtl xor isStart
+
+ val viewLocation = IntArray(2)
+ view.getLocationOnScreen(viewLocation)
+
+ val viewX = viewLocation[0]
+ val viewY = viewLocation[1]
+
+ outPoint.x = viewX + if (isLeftEdge) view.height / 2 else view.width - view.height / 2
+ outPoint.y = viewY + view.height / 2
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 685b062..32c4a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -536,6 +536,7 @@
private final FeatureFlags mFeatureFlags;
private final UnfoldTransitionConfig mUnfoldTransitionConfig;
private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
+ private final Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimation;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final MessageRouter mMessageRouter;
private final WallpaperManager mWallpaperManager;
@@ -768,6 +769,7 @@
BrightnessSlider.Factory brightnessSliderFactory,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<StatusBarMoveFromCenterAnimationController> statusBarUnfoldAnimationController,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -860,6 +862,7 @@
mBrightnessSliderFactory = brightnessSliderFactory;
mUnfoldTransitionConfig = unfoldTransitionConfig;
mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation;
+ mMoveFromCenterAnimation = statusBarUnfoldAnimationController;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mStatusBarLocationPublisher = locationPublisher;
@@ -1141,8 +1144,13 @@
sendInitialExpansionAmount(listener);
}
+ StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null;
+ if (mUnfoldTransitionConfig.isEnabled()) {
+ moveFromCenterAnimation = mMoveFromCenterAnimation.get();
+ }
mPhoneStatusBarViewController =
- new PhoneStatusBarViewController(mStatusBarView, mCommandQueue);
+ new PhoneStatusBarViewController(mStatusBarView, mCommandQueue,
+ moveFromCenterAnimation);
mPhoneStatusBarViewController.init();
mBatteryMeterViewController = new BatteryMeterViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index 6a510c9..5301b25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -57,6 +57,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -98,6 +99,7 @@
private final VibratorHelper mVibratorHelper;
private final Optional<Vibrator> mVibratorOptional;
private final LightBarController mLightBarController;
+ private final DisableFlagsLogger mDisableFlagsLogger;
private final int mDisplayId;
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
@@ -134,6 +136,7 @@
VibratorHelper vibratorHelper,
Optional<Vibrator> vibratorOptional,
LightBarController lightBarController,
+ DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId) {
mStatusBar = statusBar;
@@ -159,6 +162,7 @@
mVibratorHelper = vibratorHelper;
mVibratorOptional = vibratorOptional;
mLightBarController = lightBarController;
+ mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
@@ -267,7 +271,17 @@
if (displayId != mDisplayId) {
return;
}
+
+ int state2BeforeAdjustment = state2;
state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+ Log.d(StatusBar.TAG,
+ mDisableFlagsLogger.getDisableFlagsString(
+ /* old= */ new DisableFlagsLogger.DisableState(
+ mStatusBar.getDisabled1(), mStatusBar.getDisabled2()),
+ /* new= */ new DisableFlagsLogger.DisableState(
+ state1, state2BeforeAdjustment),
+ /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState(
+ state1, state2)));
final int old1 = mStatusBar.getDisabled1();
final int diff1 = state1 ^ old1;
@@ -277,43 +291,6 @@
final int diff2 = state2 ^ old2;
mStatusBar.setDisabled2(state2);
- if (StatusBar.DEBUG) {
- Log.d(StatusBar.TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)",
- old1, state1, diff1));
- Log.d(StatusBar.TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)",
- old2, state2, diff2));
- }
-
- StringBuilder flagdbg = new StringBuilder();
- flagdbg.append("disable<");
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_EXPAND)) ? 'E' : 'e');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_EXPAND)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) ? 'I' : 'i');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS)) ? 'A' : 'a');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO)) ? 'S' : 's');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_BACK)) ? 'B' : 'b');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_BACK)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_HOME)) ? 'H' : 'h');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_HOME)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_RECENT)) ? 'R' : 'r');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_RECENT)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_CLOCK)) ? 'C' : 'c');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_CLOCK)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH)) ? 'S' : 's');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SEARCH)) ? '!' : ' ');
- flagdbg.append("> disable2<");
- flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? 'Q' : 'q');
- flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? '!' : ' ');
- flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? 'I' : 'i');
- flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? '!' : ' ');
- flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? 'N' : 'n');
- flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? '!' : ' ');
- flagdbg.append('>');
- Log.d(StatusBar.TAG, flagdbg.toString());
-
if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
mShadeController.animateCollapsePanels();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
new file mode 100644
index 0000000..8af03aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.statusbar.phone
+
+import android.view.View
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+
+@SysUISingleton
+class StatusBarMoveFromCenterAnimationController @Inject constructor(
+ private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val windowManager: WindowManager
+) {
+
+ private lateinit var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator
+
+ fun init(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
+ moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
+ viewCenterProvider = viewCenterProvider)
+
+ unfoldTransitionProgressProvider.addCallback(object : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ moveFromCenterAnimator.updateDisplayProperties()
+
+ viewsToAnimate.forEach {
+ moveFromCenterAnimator.registerViewForAnimation(it)
+ }
+ }
+
+ override fun onTransitionFinished() {
+ moveFromCenterAnimator.onTransitionFinished()
+ moveFromCenterAnimator.clearRegisteredViews()
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ moveFromCenterAnimator.onTransitionProgress(progress)
+ }
+ })
+ }
+
+ fun onStatusBarWidthChanged() {
+ moveFromCenterAnimator.updateViewPositions()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 63ee701..befea41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -90,6 +90,7 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
+import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -213,6 +214,7 @@
BrightnessSlider.Factory brightnessSliderFactory,
UnfoldTransitionConfig unfoldTransitionConfig,
Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -307,6 +309,7 @@
brightnessSliderFactory,
unfoldTransitionConfig,
unfoldLightRevealOverlayAnimation,
+ statusBarMoveFromCenterAnimation,
ongoingCallController,
animationScheduler,
locationPublisher,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb2..8d615f0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -226,7 +226,7 @@
}
@Test
- public void testDetachRemovesSmartspaceView() {
+ public void testDetachDisconnectsSmartspace() {
when(mSmartspaceController.isEnabled()).thenReturn(true);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
@@ -237,7 +237,7 @@
verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mView).removeView(mFakeSmartspaceView);
+ verify(mSmartspaceController).disconnect();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 109721f..12c0d53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -95,6 +96,8 @@
private KeyguardBypassController mBypassController;
@Mock
private FalsingManager mFalsingManager;
+ @Mock
+ private FeatureFlags mFeatureFlags;
public QSFragmentTest() {
super(QSFragment.class);
@@ -136,7 +139,7 @@
() -> mock(AutoTileManager.class), mock(DumpManager.class),
mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
- mock(SecureSettings.class), mock(CustomTileStatePersister.class));
+ mock(SecureSettings.class), mock(CustomTileStatePersister.class), mFeatureFlags);
qs.setHost(host);
qs.setListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 6c7f770..6bb2b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -121,6 +121,8 @@
private SecureSettings mSecureSettings;
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private Handler mHandler;
private TestableLooper mLooper;
@@ -138,8 +140,9 @@
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
- mSecureSettings, mCustomTileStatePersister);
+ mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
setUpTileFactory();
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
}
private void setUpTileFactory() {
@@ -167,13 +170,13 @@
@Test
public void testLoadTileSpecs_emptySetting() {
- List<String> tiles = QSTileHost.loadTileSpecs(mContext, "");
+ List<String> tiles = QSTileHost.loadTileSpecs(mContext, "", mFeatureFlags);
assertFalse(tiles.isEmpty());
}
@Test
public void testLoadTileSpecs_nullSetting() {
- List<String> tiles = QSTileHost.loadTileSpecs(mContext, null);
+ List<String> tiles = QSTileHost.loadTileSpecs(mContext, null, mFeatureFlags);
assertFalse(tiles.isEmpty());
}
@@ -187,6 +190,55 @@
}
@Test
+ public void testRemoveWifiAndCellularWithoutInternet() {
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+ mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2");
+
+ assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
+ assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
+ assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+ }
+
+ @Test
+ public void testRemoveWifiAndCellularWithInternet() {
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+ mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet");
+
+ assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+ assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+ assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+ }
+
+ @Test
+ public void testRemoveWifiWithoutInternet() {
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+ mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2");
+
+ assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+ assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
+ assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+ }
+
+ @Test
+ public void testRemoveCellWithInternet() {
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+ mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet");
+
+ assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+ assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+ assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+ }
+
+ @Test
+ public void testNoWifiNoCellularNoInternet() {
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+ mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+
+ assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+ assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+ }
+
+ @Test
public void testSpecWithInvalidDoesNotUseDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
@@ -319,7 +371,7 @@
@Test
public void testLoadTileSpec_repeated() {
- List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
+ List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2", mFeatureFlags);
assertEquals(2, specs.size());
assertEquals("spec1", specs.get(0));
@@ -330,7 +382,7 @@
public void testLoadTileSpec_repeatedInDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
- List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
+ List<String> specs = QSTileHost.loadTileSpecs(mContext, "default", mFeatureFlags);
// Remove spurious tiles, like dbg:mem
specs.removeIf(spec -> !"spec1".equals(spec));
@@ -341,7 +393,7 @@
public void testLoadTileSpec_repeatedDefaultAndSetting() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
- List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
+ List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1", mFeatureFlags);
// Remove spurious tiles, like dbg:mem
specs.removeIf(spec -> !"spec1".equals(spec));
@@ -379,11 +431,12 @@
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
UiEventLogger uiEventLogger, UserTracker userTracker,
- SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) {
+ SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
+ FeatureFlags featureFlags) {
super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
tunerService, autoTiles, dumpManager, broadcastDispatcher,
Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings,
- customTileStatePersister);
+ customTileStatePersister, featureFlags);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 8546a35..018806e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -109,6 +109,8 @@
private PackageManager mPackageManager;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
@@ -134,12 +136,12 @@
}
}
).when(mQSTileHost).createTile(anyString());
-
+ when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
FakeSystemClock clock = new FakeSystemClock();
mMainExecutor = new FakeExecutor(clock);
mBgExecutor = new FakeExecutor(clock);
mTileQueryHelper = new TileQueryHelper(
- mContext, mUserTracker, mMainExecutor, mBgExecutor);
+ mContext, mUserTracker, mMainExecutor, mBgExecutor, mFeatureFlags);
mTileQueryHelper.setListener(mListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 2b18404..e3045eb2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -98,6 +99,8 @@
private UserTracker mUserTracker;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Before
public void setUp() throws Exception {
@@ -119,7 +122,8 @@
mUiEventLogger,
mUserTracker,
mSecureSettings,
- mock(CustomTileStatePersister.class));
+ mock(CustomTileStatePersister.class),
+ mFeatureFlags);
mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
mUserTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index ebc6f2a..6a68b71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -157,6 +157,21 @@
assertThat(view.translationY).isWithin(0.01f).of(3.75f)
}
+ @Test
+ fun testUpdateViewPositions_viewOnTheLeftAndMovedToTheRight_viewTranslatedToTheLeft() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+ animator.onTransitionProgress(0.5f)
+ view.updateMock(x = 80) // view moved from the left side to the right
+
+ animator.updateViewPositions()
+
+ // Negative translationX -> translated to the left
+ assertThat(view.translationX).isWithin(0.1f).of(-5.25f)
+ }
+
private fun createView(
x: Int = 0,
y: Int = 0,
@@ -176,7 +191,30 @@
whenever(view.width).thenReturn(width)
whenever(view.height).thenReturn(height)
- return view.apply {
+ view.updateMock(x, y, width, height, translationX, translationY)
+
+ return view
+ }
+
+ private fun View.updateMock(
+ x: Int = 0,
+ y: Int = 0,
+ width: Int = 10,
+ height: Int = 10,
+ translationX: Float = 0f,
+ translationY: Float = 0f
+ ) {
+ doAnswer {
+ val location = (it.arguments[0] as IntArray)
+ location[0] = x
+ location[1] = y
+ Unit
+ }.`when`(this).getLocationOnScreen(any())
+
+ whenever(this.width).thenReturn(width)
+ whenever(this.height).thenReturn(height)
+
+ this.apply {
setTranslationX(translationX)
setTranslationY(translationY)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
new file mode 100644
index 0000000..096efad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 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.statusbar
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+
+@SmallTest
+class DisableFlagsLoggerTest : SysuiTestCase() {
+ private val disable1Flags = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'B', 'b'),
+ DisableFlagsLogger.DisableFlag(0b001, 'C', 'c'),
+ )
+ private val disable2Flags = listOf(
+ DisableFlagsLogger.DisableFlag(0b10, 'M', 'm'),
+ DisableFlagsLogger.DisableFlag(0b01, 'N', 'n'),
+ )
+
+ private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags)
+
+ @Test
+ fun getDisableFlagsString_oldAndNewSame_statesLoggedButDiffsNotLogged() {
+ val state = DisableFlagsLogger.DisableState(
+ 0b111, // ABC
+ 0b01 // mN
+ )
+
+ val result = disableFlagsLogger.getDisableFlagsString(state, state)
+
+ assertThat(result).contains("Old: ABC.mN")
+ assertThat(result).contains("New: ABC.mN")
+ assertThat(result).doesNotContain("(")
+ assertThat(result).doesNotContain(")")
+ }
+
+ @Test
+ fun getDisableFlagsString_oldAndNewDifferent_statesAndDiffLogged() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(
+ 0b111, // ABC
+ 0b01, // mN
+ ),
+ DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b10 // Mn
+ )
+ )
+
+ assertThat(result).contains("Old: ABC.mN")
+ assertThat(result).contains("New: abC.Mn")
+ assertThat(result).contains("(ab.Mn)")
+ }
+
+ @Test
+ fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b01, // mN
+ ),
+ DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b00 // mn
+ )
+ )
+
+ assertThat(result).contains("(.n)")
+ }
+
+ @Test
+ fun getDisableFlagsString_nullLocalModification_localModNotLogged() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(0, 0),
+ DisableFlagsLogger.DisableState(1, 1),
+ newAfterLocalModification = null
+ )
+
+ assertThat(result).doesNotContain("local modification")
+ }
+
+ @Test
+ fun getDisableFlagsString_newAfterLocalModificationSameAsNew_localModNotLogged() {
+ val newState = DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b10 // mn
+ )
+
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(0, 0), newState, newState
+ )
+
+ assertThat(result).doesNotContain("local modification")
+ }
+
+ @Test
+ fun getDisableFlagsString_newAfterLocalModificationDifferent_localModAndDiffLogged() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ old = DisableFlagsLogger.DisableState(0, 0),
+ new = DisableFlagsLogger.DisableState(
+ 0b000, // abc
+ 0b00 // mn
+ ),
+ newAfterLocalModification = DisableFlagsLogger.DisableState(
+ 0b100, // Abc
+ 0b10 // Mn
+ )
+ )
+
+ assertThat(result).contains("local modification: Abc.Mn (A.M)")
+ }
+
+ @Test
+ fun constructor_defaultDisableFlags_noException() {
+ // Just creating the logger with the default params will trigger the exception if there
+ // is one.
+ DisableFlagsLogger()
+ }
+
+ @Test
+ fun constructor_disable1_FlagIsSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'),
+ ),
+ listOf()
+ )
+ }
+ }
+
+ @Test
+ fun constructor_disable1_FlagNotSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'),
+ ),
+ listOf()
+ )
+ }
+ }
+
+ @Test
+ fun constructor_disable2_FlagIsSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(),
+ disable2FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'),
+ ),
+ )
+ }
+ }
+
+ @Test
+ fun constructor_disable2_FlagNotSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(),
+ disable2FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'),
+ ),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index d63730d..c7d4794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.phone
+import android.view.LayoutInflater
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
import com.google.common.truth.Truth.assertThat
@@ -24,7 +27,10 @@
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import com.android.systemui.R
+import com.android.systemui.util.mockito.any
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@@ -32,14 +38,22 @@
@Mock
private lateinit var commandQueue: CommandQueue
+ @Mock
+ private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
+
private lateinit var view: PhoneStatusBarView
private lateinit var controller: PhoneStatusBarViewController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- view = PhoneStatusBarView(mContext, null)
- controller = PhoneStatusBarViewController(view, commandQueue)
+ // create the view on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ val parent = FrameLayout(mContext) // add parent to keep layout params
+ view = LayoutInflater.from(mContext)
+ .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ }
+ controller = PhoneStatusBarViewController(view, commandQueue, null)
}
@Test
@@ -56,4 +70,11 @@
assertThat(providerUsed).isTrue()
}
+
+ @Test
+ fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() {
+ controller = PhoneStatusBarViewController(view, commandQueue, moveFromCenterAnimation)
+
+ verify(moveFromCenterAnimation).init(any(), any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
index 52538c7..8555306 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -112,6 +113,7 @@
mVibratorHelper,
Optional.of(mVibrator),
mLightBarController,
+ new DisableFlagsLogger(),
DEFAULT_DISPLAY);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index b23414b..3c0382b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -254,6 +254,7 @@
@Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
@Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
@Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
+ @Mock private Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimationLazy;
@Mock private OngoingCallController mOngoingCallController;
@Mock private SystemStatusAnimationScheduler mAnimationScheduler;
@Mock private StatusBarLocationPublisher mLocationPublisher;
@@ -428,6 +429,7 @@
mBrightnessSliderFactory,
mUnfoldTransitionConfig,
mUnfoldLightRevealOverlayAnimationLazy,
+ mMoveFromCenterAnimationLazy,
mOngoingCallController,
mAnimationScheduler,
mLocationPublisher,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 30de4b4..4748a86 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -566,7 +566,7 @@
return PendingIntent.getActivityAsUser(getContext(),
0 /* request code */,
NotificationAccessConfirmationActivityContract.launcherIntent(
- userId, component, packageTitle),
+ getContext(), userId, component, packageTitle),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT,
null /* options */,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b9b90c0..b230200 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -200,7 +200,8 @@
*/
public class AudioService extends IAudioService.Stub
implements AccessibilityManager.TouchExplorationStateChangeListener,
- AccessibilityManager.AccessibilityServicesStateChangeListener {
+ AccessibilityManager.AccessibilityServicesStateChangeListener,
+ AudioSystemAdapter.OnRoutingUpdatedListener {
private static final String TAG = "AS.AudioService";
@@ -314,12 +315,14 @@
private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38;
private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
private static final int MSG_DISPATCH_AUDIO_MODE = 40;
+ private static final int MSG_ROUTING_UPDATED = 41;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
private static final int MSG_INIT_STREAMS_VOLUMES = 101;
+ private static final int MSG_INIT_SPATIALIZER = 102;
// end of messages handled under wakelock
// retry delay in case of failure to indicate system ready to AudioFlinger
@@ -869,6 +872,8 @@
mSfxHelper = new SoundEffectsHelper(mContext);
+ mSpatializerHelper = new SpatializerHelper(this, mAudioSystem);
+
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
@@ -1033,6 +1038,9 @@
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
+ queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
+ 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
+
}
/**
@@ -1222,6 +1230,22 @@
updateVibratorInfos();
}
+ //-----------------------------------------------------------------
+ // routing monitoring from AudioSystemAdapter
+ @Override
+ public void onRoutingUpdatedFromNative() {
+ sendMsg(mAudioHandler,
+ MSG_ROUTING_UPDATED,
+ SENDMSG_REPLACE, 0, 0, null,
+ /*delay*/ 0);
+ }
+
+ void monitorRoutingChanges(boolean enabled) {
+ mAudioSystem.setRoutingListener(enabled ? this : null);
+ }
+
+
+ //-----------------------------------------------------------------
RoleObserver mRoleObserver;
class RoleObserver implements OnRoleHoldersChangedListener {
@@ -1406,6 +1430,9 @@
}
}
+ // TODO check property if feature enabled
+ mSpatializerHelper.reset(/* featureEnabled */ SPATIALIZER_FEATURE_ENABLED_DEFAULT);
+
onIndicateSystemReady();
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
@@ -7539,6 +7566,13 @@
mAudioEventWakeLock.release();
break;
+ case MSG_INIT_SPATIALIZER:
+ mSpatializerHelper.init();
+ // TODO read property to see if enabled
+ mSpatializerHelper.setFeatureEnabled(SPATIALIZER_FEATURE_ENABLED_DEFAULT);
+ mAudioEventWakeLock.release();
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -7671,6 +7705,10 @@
case MSG_DISPATCH_AUDIO_MODE:
dispatchMode(msg.arg1);
break;
+
+ case MSG_ROUTING_UPDATED:
+ mSpatializerHelper.onRoutingUpdated();
+ break;
}
}
}
@@ -8239,7 +8277,8 @@
}
//==========================================================================================
- private final SpatializerHelper mSpatializerHelper = new SpatializerHelper();
+ private final @NonNull SpatializerHelper mSpatializerHelper;
+ private static final boolean SPATIALIZER_FEATURE_ENABLED_DEFAULT = false;
private void enforceModifyDefaultAudioEffectsPermission() {
if (mContext.checkCallingOrSelfPermission(
@@ -8249,9 +8288,12 @@
}
}
- /** @see AudioManager#getSpatializerImmersiveAudioLevel() */
+ /**
+ * Returns the immersive audio level that the platform is capable of
+ * @see Spatializer#getImmersiveAudioLevel()
+ */
public int getSpatializerImmersiveAudioLevel() {
- return mSpatializerHelper.getImmersiveAudioLevel();
+ return mSpatializerHelper.getCapableImmersiveAudioLevel();
}
/** @see Spatializer#isEnabled() */
@@ -8267,7 +8309,7 @@
/** @see Spatializer#setSpatializerEnabled(boolean) */
public void setSpatializerEnabled(boolean enabled) {
enforceModifyDefaultAudioEffectsPermission();
- mSpatializerHelper.setEnabled(enabled);
+ mSpatializerHelper.setFeatureEnabled(enabled);
}
/** @see Spatializer#canBeSpatialized() */
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 6d56780..ac212ee 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -17,6 +17,7 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioSystem;
@@ -24,6 +25,8 @@
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +62,9 @@
private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
private int[] mMethodCacheHit;
+ private static final Object sRoutingListenerLock = new Object();
+ @GuardedBy("sRoutingListenerLock")
+ private static @Nullable OnRoutingUpdatedListener sRoutingListener;
/**
* should be false except when trying to debug caching errors. When true, the value retrieved
@@ -76,6 +82,23 @@
Log.d(TAG, "---- onRoutingUpdated (from native) ----------");
}
invalidateRoutingCache();
+ final OnRoutingUpdatedListener listener;
+ synchronized (sRoutingListenerLock) {
+ listener = sRoutingListener;
+ }
+ if (listener != null) {
+ listener.onRoutingUpdatedFromNative();
+ }
+ }
+
+ interface OnRoutingUpdatedListener {
+ void onRoutingUpdatedFromNative();
+ }
+
+ static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) {
+ synchronized (sRoutingListenerLock) {
+ sRoutingListener = listener;
+ }
}
/**
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 708d9e1..32ac785 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -17,9 +17,13 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioFormat;
+import android.media.AudioSystem;
+import android.media.INativeSpatializerCallback;
+import android.media.ISpatializer;
import android.media.ISpatializerCallback;
import android.media.Spatializer;
import android.os.RemoteCallbackList;
@@ -28,6 +32,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
/**
* A helper class to manage Spatializer related functionality
@@ -35,12 +40,175 @@
public class SpatializerHelper {
private static final String TAG = "AS.SpatializerHelper";
+ private static final boolean DEBUG = true;
+
+ private static void logd(String s) {
+ if (DEBUG) {
+ Log.i(TAG, s);
+ }
+ }
+
+ private final @NonNull AudioSystemAdapter mASA;
+ private final @NonNull AudioService mAudioService;
+
+ //------------------------------------------------------------
+ // 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;
+ private int mState = STATE_UNINITIALIZED;
+
+ /** current level as reported by native Spatializer in callback */
+ private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ private @Nullable ISpatializer mSpat;
+ private @Nullable SpatializerCallback mSpatCallback;
+
+ // default attributes and format that determine basic availability of spatialization
+ private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+ private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .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];
//---------------------------------------------------------------
// audio device compatibility / enabled
private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0);
+ //------------------------------------------------------
+ // initialization
+ SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) {
+ mAudioService = mother;
+ mASA = asa;
+ }
+
+ synchronized void init() {
+ Log.i(TAG, "Initializing");
+ if (mState != STATE_UNINITIALIZED) {
+ throw new IllegalStateException(("init() called in state:" + mState));
+ }
+ // is there a spatializer?
+ mSpatCallback = new SpatializerCallback();
+ final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback);
+ if (spat == null) {
+ Log.i(TAG, "init(): No Spatializer found");
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ // capabilities of spatializer?
+ try {
+ byte[] levels = spat.getSupportedLevels();
+ if (levels == null
+ || levels.length == 0
+ || (levels.length == 1
+ && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) {
+ Log.e(TAG, "Spatializer is useless");
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ for (byte level : levels) {
+ logd("found support for level: " + level);
+ if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) {
+ logd("Setting Spatializer to LEVEL_MULTICHANNEL");
+ mCapableSpatLevel = level;
+ break;
+ }
+ }
+ } catch (RemoteException e) {
+ /* capable level remains at NONE*/
+ } finally {
+ if (spat != null) {
+ try {
+ spat.release();
+ } catch (RemoteException e) { /* capable level remains at NONE*/ }
+ }
+ }
+ if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ mState = STATE_DISABLED_UNAVAILABLE;
+ // note at this point mSpat is still not instantiated
+ }
+
+ /**
+ * Like init() but resets the state and spatializer levels
+ * @param featureEnabled
+ */
+ synchronized void reset(boolean featureEnabled) {
+ Log.i(TAG, "Resetting");
+ mState = STATE_UNINITIALIZED;
+ mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ init();
+ setFeatureEnabled(featureEnabled);
+ }
+
+ //------------------------------------------------------
+ // routing monitoring
+ void onRoutingUpdated() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ return;
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ break;
+ }
+ mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
+ final boolean able =
+ AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
+ logd("onRoutingUpdated: can spatialize media 5.1:" + able
+ + " on device:" + ROUTING_DEVICES[0]);
+ setDispatchAvailableState(able);
+ }
+
+ //------------------------------------------------------
+ // spatializer callback from native
+ private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
+
+ public void onLevelChanged(byte level) {
+ logd("SpatializerCallback.onLevelChanged level:" + level);
+ synchronized (SpatializerHelper.this) {
+ mSpatLevel = level;
+ }
+ // TODO use reported spat level to change state
+ }
+
+ public void onHeadTrackingModeChanged(byte mode) {
+ logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + mode);
+ }
+
+ public void onHeadToSoundStagePoseUpdated(float[] headToStage) {
+ if (headToStage == null) {
+ Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform");
+ return;
+ }
+ if (DEBUG) {
+ // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
+ StringBuilder t = new StringBuilder(42);
+ for (float val : headToStage) {
+ t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]");
+ }
+ logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t);
+ }
+ }
+ };
+
+ //------------------------------------------------------
+ // compatible devices
/**
* @return a shallow copy of the list of compatible audio devices
*/
@@ -59,37 +227,72 @@
}
//------------------------------------------------------
- // enabled state
-
- // global state of feature
- boolean mFeatureEnabled = false;
- // initialized state, checked after each audio_server start
- boolean mInitialized = false;
+ // states
synchronized boolean isEnabled() {
- return mFeatureEnabled;
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ return false;
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ default:
+ return true;
+ }
}
synchronized boolean isAvailable() {
- if (!mInitialized) {
- return false;
- }
- // TODO check device compatibility
- // ...
- return true;
- }
-
- synchronized void setEnabled(boolean enabled) {
- final boolean oldState = mFeatureEnabled;
- mFeatureEnabled = enabled;
- if (oldState != enabled) {
- dispatchEnabledState();
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ return false;
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ default:
+ return true;
}
}
- public int getImmersiveAudioLevel() {
- // TODO replace placeholder code with actual effect discovery
- return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ synchronized void setFeatureEnabled(boolean enabled) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ if (enabled) {
+ throw(new IllegalStateException("Can't enable when uninitialized"));
+ }
+ return;
+ case STATE_NOT_SUPPORTED:
+ if (enabled) {
+ Log.e(TAG, "Can't enable when unsupported");
+ }
+ return;
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ if (enabled) {
+ createSpat();
+ break;
+ } else {
+ // already in disabled state
+ return;
+ }
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (!enabled) {
+ releaseSpat();
+ break;
+ } else {
+ // already in enabled state
+ return;
+ }
+ }
+ setDispatchFeatureEnabledState(enabled);
+ }
+
+ synchronized int getCapableImmersiveAudioLevel() {
+ return mCapableSpatLevel;
}
final RemoteCallbackList<ISpatializerCallback> mStateCallbacks =
@@ -105,12 +308,94 @@
mStateCallbacks.unregister(callback);
}
- private synchronized void dispatchEnabledState() {
+ /**
+ * precondition: mState = STATE_*
+ * isFeatureEnabled() != featureEnabled
+ * @param featureEnabled
+ */
+ private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) {
+ if (featureEnabled) {
+ switch (mState) {
+ case STATE_DISABLED_UNAVAILABLE:
+ mState = STATE_ENABLED_UNAVAILABLE;
+ break;
+ case STATE_DISABLED_AVAILABLE:
+ mState = STATE_ENABLED_AVAILABLE;
+ break;
+ default:
+ throw(new IllegalStateException("Invalid mState:" + mState
+ + " for enabled true"));
+ }
+ } else {
+ switch (mState) {
+ case STATE_ENABLED_UNAVAILABLE:
+ mState = STATE_DISABLED_UNAVAILABLE;
+ break;
+ case STATE_ENABLED_AVAILABLE:
+ mState = STATE_DISABLED_AVAILABLE;
+ break;
+ default:
+ throw (new IllegalStateException("Invalid mState:" + mState
+ + " for enabled false"));
+ }
+ }
final int nbCallbacks = mStateCallbacks.beginBroadcast();
for (int i = 0; i < nbCallbacks; i++) {
try {
mStateCallbacks.getBroadcastItem(i)
- .dispatchSpatializerEnabledChanged(mFeatureEnabled);
+ .dispatchSpatializerEnabledChanged(featureEnabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
+ }
+ }
+ mStateCallbacks.finishBroadcast();
+ // TODO persist enabled state
+ }
+
+ private synchronized void setDispatchAvailableState(boolean available) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw(new IllegalStateException(
+ "Should not update available state in state:" + mState));
+ case STATE_DISABLED_UNAVAILABLE:
+ if (available) {
+ mState = STATE_DISABLED_AVAILABLE;
+ break;
+ } else {
+ // already in unavailable state
+ return;
+ }
+ case STATE_ENABLED_UNAVAILABLE:
+ if (available) {
+ mState = STATE_ENABLED_AVAILABLE;
+ break;
+ } else {
+ // already in unavailable state
+ return;
+ }
+ case STATE_DISABLED_AVAILABLE:
+ if (available) {
+ // already in available state
+ return;
+ } else {
+ mState = STATE_DISABLED_UNAVAILABLE;
+ break;
+ }
+ case STATE_ENABLED_AVAILABLE:
+ if (available) {
+ // already in available state
+ return;
+ } else {
+ mState = STATE_ENABLED_UNAVAILABLE;
+ break;
+ }
+ }
+ final int nbCallbacks = mStateCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mStateCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerAvailableChanged(available);
} catch (RemoteException e) {
Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
}
@@ -119,10 +404,72 @@
}
//------------------------------------------------------
+ // native Spatializer management
+
+ /**
+ * precondition: mState == STATE_DISABLED_*
+ */
+ private void createSpat() {
+ if (mSpat == null) {
+ mSpatCallback = new SpatializerCallback();
+ mSpat = AudioSystem.getSpatializer(mSpatCallback);
+ try {
+ mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't set spatializer level", e);
+ mState = STATE_NOT_SUPPORTED;
+ mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ }
+ }
+ }
+
+ /**
+ * precondition: mState == STATE_ENABLED_*
+ */
+ private void releaseSpat() {
+ if (mSpat != null) {
+ mSpatCallback = null;
+ try {
+ mSpat.release();
+ mSpat = null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't set release spatializer cleanly", e);
+ }
+ }
+ }
+
+ //------------------------------------------------------
// virtualization capabilities
synchronized boolean canBeSpatialized(
@NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
- // TODO hook up to spatializer effect for query
- return false;
+ logd("canBeSpatialized usage:" + attributes.getUsage()
+ + " format:" + format.toLogFriendlyString());
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ logd("canBeSpatialized false due to state:" + mState);
+ return false;
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ break;
+ }
+
+ // filter on AudioAttributes usage
+ switch (attributes.getUsage()) {
+ case AudioAttributes.USAGE_MEDIA:
+ case AudioAttributes.USAGE_GAME:
+ break;
+ default:
+ logd("canBeSpatialized false due to usage:" + attributes.getUsage());
+ return false;
+ }
+ AudioDeviceAttributes[] devices =
+ // going through adapter to take advantage of routing cache
+ (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+ final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
+ logd("canBeSpatialized returning " + able);
+ return able;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 73bcea6..827523b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2252,6 +2252,9 @@
int displayId = msg.arg1;
final LogicalDisplay display =
mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (display == null) {
+ break;
+ }
uids = display.getPendingFrameRateOverrideUids();
display.clearPendingFrameRateOverrideUids();
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a98a4ec..a124f76 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4945,10 +4945,12 @@
* this has become invisible.
*/
private void postApplyAnimation(boolean visible) {
+ final boolean usingShellTransitions =
+ mAtmService.getTransitionController().getTransitionPlayer() != null;
final boolean delayed = isAnimating(PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
- if (!delayed) {
+ if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
// which we might have done because we were in closing/opening apps list.
@@ -4967,8 +4969,8 @@
// updated.
// If we're becoming invisible, update the client visibility if we are not running an
// animation. Otherwise, we'll update client visibility in onAnimationFinished.
- if (visible || !isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+ if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+ || usingShellTransitions) {
setClientVisible(visible);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3b43e48..c721c24 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -30,7 +30,6 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -1573,11 +1572,7 @@
newTransition.setRemoteTransition(remoteTransition);
}
mService.getTransitionController().collect(r);
- // TODO(b/188669821): Remove when navbar reparenting moves to shell
- if (r.getActivityType() == ACTIVITY_TYPE_HOME && r.getOptions() != null
- && r.getOptions().getTransientLaunch()) {
- mService.getTransitionController().setIsLegacyRecents();
- }
+ final boolean isTransient = r.getOptions() != null && r.getOptions().getTransientLaunch();
try {
mService.deferWindowLayout();
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
@@ -1625,6 +1620,11 @@
// it as an existence change.
mService.getTransitionController().collectExistenceChange(r);
}
+ if (isTransient) {
+ // `r` isn't guaranteed to be the actual relevant activity, so we must wait
+ // until after we launched to identify the relevant activity.
+ mService.getTransitionController().setTransientLaunch(mLastStartActivityRecord);
+ }
if (newTransition != null) {
mService.getTransitionController().requestStartTransition(newTransition,
mTargetTask, remoteTransition);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 5174a38..0ba77d8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -30,6 +30,7 @@
import android.content.res.CompatibilityInfo;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.RemoteException;
import android.service.voice.IVoiceInteractionSession;
import android.util.IntArray;
@@ -611,6 +612,14 @@
PackageConfigurationUpdater setNightMode(int nightMode);
/**
+ * Sets the app-specific locales for the application referenced by this updater.
+ * This setting is persisted and will overlay on top of the system locales for
+ * the said application.
+ * @return the current {@link PackageConfigurationUpdater} updated with the provided locale.
+ */
+ PackageConfigurationUpdater setLocales(LocaleList locales);
+
+ /**
* Commit changes.
*/
void commit();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 859107c..1c8f6f1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -956,7 +956,7 @@
setRecentTasks(new RecentTasks(this, mTaskSupervisor));
mVrController = new VrController(mGlobalLock);
mKeyguardController = mTaskSupervisor.getKeyguardController();
- mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
+ mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue, this);
}
public void onActivityManagerInternalAdded() {
@@ -6575,7 +6575,8 @@
final class PackageConfigurationUpdaterImpl implements
ActivityTaskManagerInternal.PackageConfigurationUpdater {
private final int mPid;
- private int mNightMode;
+ private Integer mNightMode;
+ private LocaleList mLocales;
PackageConfigurationUpdaterImpl(int pid) {
mPid = pid;
@@ -6588,6 +6589,13 @@
}
@Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater
+ setLocales(LocaleList locales) {
+ mLocales = locales;
+ return this;
+ }
+
+ @Override
public void commit() {
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
@@ -6597,8 +6605,10 @@
Slog.w(TAG, "Override application configuration: cannot find pid " + mPid);
return;
}
- wpc.setOverrideNightMode(mNightMode);
- wpc.updateNightModeForAllActivities(mNightMode);
+ LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists(
+ mLocales, getGlobalConfiguration().getLocales());
+ wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+ wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride);
mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -6606,8 +6616,12 @@
}
}
- int getNightMode() {
+ Integer getNightMode() {
return mNightMode;
}
+
+ LocaleList getLocales() {
+ return mLocales;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 6fafc02..eeb85c5 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -39,6 +39,7 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.LocaleList;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -512,7 +513,7 @@
return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
}
- /** Returns the activity type associated with the the configuration container. */
+ /** Returns the activity type associated with the configuration container. */
/*@WindowConfiguration.ActivityType*/
public int getActivityType() {
return mFullConfiguration.windowConfiguration.getActivityType();
@@ -546,20 +547,48 @@
}
/**
+ * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
+ * @return true if any of the requested configuration has been updated.
+ */
+ public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+ mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+ boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
+ nightMode);
+ boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
+ locales);
+ if (newNightModeSet || newLocalesSet) {
+ onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+ }
+ return newNightModeSet || newLocalesSet;
+ }
+
+ /**
* Overrides the night mode applied to this ConfigurationContainer.
* @return true if the nightMode has been changed.
*/
- public boolean setOverrideNightMode(int nightMode) {
+ private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) {
final int currentUiMode = mRequestedOverrideConfiguration.uiMode;
final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK;
final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode == validNightMode) {
return false;
}
- mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
- mRequestsTmpConfig.uiMode = validNightMode
+ requestsTmpConfig.uiMode = validNightMode
| (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
- onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+ return true;
+ }
+
+ /**
+ * Overrides the locales applied to this ConfigurationContainer.
+ * @return true if the LocaleList has been changed.
+ */
+ private boolean setOverrideLocales(Configuration requestsTmpConfig,
+ @NonNull LocaleList overrideLocales) {
+ if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) {
+ return false;
+ }
+ requestsTmpConfig.setLocales(overrideLocales);
+ requestsTmpConfig.userSetLocale = true;
return true;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 199159e..63f6387 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -578,6 +578,7 @@
* Specifies the count to determine whether to defer updating the IME target until ready.
*/
private int mDeferUpdateImeTargetCount;
+ private boolean mUpdateImeRequestedWhileDeferred;
private MagnificationSpec mMagnificationSpec;
@@ -3729,6 +3730,7 @@
final WindowState curTarget = mImeLayeringTarget;
if (!canUpdateImeTarget()) {
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target");
+ mUpdateImeRequestedWhileDeferred = true;
return curTarget;
}
@@ -4991,6 +4993,9 @@
* Increment the deferral count to determine whether to update the IME target.
*/
void deferUpdateImeTarget() {
+ if (mDeferUpdateImeTargetCount == 0) {
+ mUpdateImeRequestedWhileDeferred = false;
+ }
mDeferUpdateImeTargetCount++;
}
@@ -5004,7 +5009,7 @@
}
mDeferUpdateImeTargetCount--;
- if (mDeferUpdateImeTargetCount == 0) {
+ if (mDeferUpdateImeTargetCount == 0 && mUpdateImeRequestedWhileDeferred) {
computeImeTarget(true /* updateImeTarget */);
}
}
diff --git a/services/core/java/com/android/server/wm/LocaleOverlayHelper.java b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
new file mode 100644
index 0000000..a1a01db
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.server.wm;
+
+import android.os.LocaleList;
+
+import java.util.Locale;
+
+/**
+ * Static utilities to overlay locales on top of another localeList.
+ *
+ * <p>This is used to overlay application-specific locales in
+ * {@link com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater} on top of
+ * system locales.
+ */
+final class LocaleOverlayHelper {
+
+ /**
+ * Combines the overlay locales and base locales.
+ * @return the combined {@link LocaleList} if the overlay locales is not empty/null else
+ * returns the empty/null LocaleList.
+ */
+ static LocaleList combineLocalesIfOverlayExists(LocaleList overlayLocales,
+ LocaleList baseLocales) {
+ if (overlayLocales == null || overlayLocales.isEmpty()) {
+ return overlayLocales;
+ }
+ return combineLocales(overlayLocales, baseLocales);
+ }
+
+ /**
+ * Creates a combined {@link LocaleList} by placing overlay locales before base locales and
+ * dropping duplicates from the base locales.
+ */
+ private static LocaleList combineLocales(LocaleList overlayLocales, LocaleList baseLocales) {
+ Locale[] combinedLocales = new Locale[overlayLocales.size() + baseLocales.size()];
+ for (int i = 0; i < overlayLocales.size(); i++) {
+ combinedLocales[i] = overlayLocales.get(i);
+ }
+ for (int i = 0; i < baseLocales.size(); i++) {
+ combinedLocales[i + overlayLocales.size()] = baseLocales.get(i);
+ }
+ // Constructor of {@link LocaleList} removes duplicates
+ return new LocaleList(combinedLocales);
+ }
+
+
+}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 1552a96..505c4beb 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.os.Environment;
+import android.os.LocaleList;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -54,12 +55,14 @@
private static final String TAG_CONFIG = "config";
private static final String ATTR_PACKAGE_NAME = "package_name";
private static final String ATTR_NIGHT_MODE = "night_mode";
+ private static final String ATTR_LOCALES = "locale_list";
private static final String PACKAGE_DIRNAME = "package_configs";
private static final String SUFFIX_FILE_NAME = "_config.xml";
private final PersisterQueue mPersisterQueue;
private final Object mLock = new Object();
+ private final ActivityTaskManagerService mAtm;
@GuardedBy("mLock")
private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
@@ -72,8 +75,9 @@
return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
}
- PackageConfigPersister(PersisterQueue queue) {
+ PackageConfigPersister(PersisterQueue queue, ActivityTaskManagerService atm) {
mPersisterQueue = queue;
+ mAtm = atm;
}
@GuardedBy("mLock")
@@ -100,7 +104,8 @@
final TypedXmlPullParser in = Xml.resolvePullParser(is);
int event;
String packageName = null;
- int nightMode = MODE_NIGHT_AUTO;
+ Integer nightMode = null;
+ LocaleList locales = null;
while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
&& event != XmlPullParser.END_TAG) {
final String name = in.getName();
@@ -120,6 +125,9 @@
case ATTR_NIGHT_MODE:
nightMode = Integer.parseInt(attrValue);
break;
+ case ATTR_LOCALES:
+ locales = LocaleList.forLanguageTags(attrValue);
+ break;
}
}
}
@@ -130,6 +138,7 @@
final PackageConfigRecord initRecord =
findRecordOrCreate(mModified, packageName, userId);
initRecord.mNightMode = nightMode;
+ initRecord.mLocales = locales;
if (DEBUG) {
Slog.d(TAG, "loadPackages: load one package " + initRecord);
}
@@ -155,7 +164,9 @@
"updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
}
if (modifiedRecord != null) {
- container.setOverrideNightMode(modifiedRecord.mNightMode);
+ container.applyAppSpecificConfig(modifiedRecord.mNightMode,
+ LocaleOverlayHelper.combineLocalesIfOverlayExists(
+ modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
}
}
}
@@ -165,10 +176,16 @@
ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
synchronized (mLock) {
PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
- record.mNightMode = impl.getNightMode();
-
- if (record.isResetNightMode()) {
- removePackage(record.mName, record.mUserId);
+ if (impl.getNightMode() != null) {
+ record.mNightMode = impl.getNightMode();
+ }
+ if (impl.getLocales() != null) {
+ record.mLocales = impl.getLocales();
+ }
+ if ((record.mNightMode == null || record.isResetNightMode())
+ && (record.mLocales == null || record.mLocales.isEmpty())) {
+ // if all values default to system settings, we can remove the package.
+ removePackage(packageName, userId);
} else {
final PackageConfigRecord pendingRecord =
findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -179,10 +196,11 @@
} else {
writeRecord = pendingRecord;
}
- if (writeRecord.mNightMode == record.mNightMode) {
+
+ if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
return;
}
- writeRecord.mNightMode = record.mNightMode;
+
if (DEBUG) {
Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
}
@@ -191,6 +209,22 @@
}
}
+ private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+ if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+ return false;
+ }
+ writeRecord.mNightMode = record.mNightMode;
+ return true;
+ }
+
+ private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+ if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+ return false;
+ }
+ writeRecord.mLocales = record.mLocales;
+ return true;
+ }
+
@GuardedBy("mLock")
void removeUser(int userId) {
synchronized (mLock) {
@@ -210,7 +244,7 @@
@GuardedBy("mLock")
void onPackageUninstall(String packageName) {
synchronized (mLock) {
- for (int i = mModified.size() - 1; i > 0; i--) {
+ for (int i = mModified.size() - 1; i >= 0; i--) {
final int userId = mModified.keyAt(i);
removePackage(packageName, userId);
}
@@ -242,7 +276,8 @@
static class PackageConfigRecord {
final String mName;
final int mUserId;
- int mNightMode;
+ Integer mNightMode;
+ LocaleList mLocales;
PackageConfigRecord(String name, int userId) {
mName = name;
@@ -256,7 +291,7 @@
@Override
public String toString() {
return "PackageConfigRecord package name: " + mName + " userId " + mUserId
- + " nightMode " + mNightMode;
+ + " nightMode " + mNightMode + " locales " + mLocales;
}
}
@@ -369,7 +404,13 @@
}
xmlSerializer.startTag(null, TAG_CONFIG);
xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
- xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+ if (mRecord.mNightMode != null) {
+ xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+ }
+ if (mRecord.mLocales != null) {
+ xmlSerializer.attribute(null, ATTR_LOCALES, mRecord.mLocales
+ .toLanguageTags());
+ }
xmlSerializer.endTag(null, TAG_CONFIG);
xmlSerializer.endDocument();
xmlSerializer.flush();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6b93364..1a46d0f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -148,6 +148,9 @@
*/
private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
+ /** Set of transient activities (lifecycle initially tied to this transition). */
+ private ArraySet<ActivityRecord> mTransientLaunches = null;
+
/** Custom activity-level animation options and callbacks. */
private TransitionInfo.AnimationOptions mOverrideOptions;
private IRemoteCallback mClientAnimationStartCallback = null;
@@ -174,6 +177,20 @@
mFlags |= flag;
}
+ /** Records an activity as transient-launch. This activity must be already collected. */
+ void setTransientLaunch(@NonNull ActivityRecord activity) {
+ if (mTransientLaunches == null) {
+ mTransientLaunches = new ArraySet<>();
+ }
+ mTransientLaunches.add(activity);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+ + "transient-launch", mSyncId, activity);
+ }
+
+ boolean isTransientLaunch(@NonNull ActivityRecord activity) {
+ return mTransientLaunches != null && mTransientLaunches.contains(activity);
+ }
+
@VisibleForTesting
int getSyncId() {
return mSyncId;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 69e6a54..c1d0f80 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -185,6 +186,20 @@
return false;
}
+ /**
+ * @return {@code true} if {@param ar} is part of a transient-launch activity in an active
+ * transition.
+ */
+ boolean isTransientLaunch(@NonNull ActivityRecord ar) {
+ if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) {
+ return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
+ }
+ return false;
+ }
+
@WindowManager.TransitionType
int getCollectingTransitionType() {
return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
@@ -331,13 +346,18 @@
}
/**
- * Explicitly mark the collectingTransition as being part of recents gesture. Used for legacy
- * behaviors.
- * TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+ * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently
+ * tied to the transition).
*/
- void setIsLegacyRecents() {
+ void setTransientLaunch(@NonNull ActivityRecord activity) {
if (mCollectingTransition == null) return;
- mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+ mCollectingTransition.setTransientLaunch(activity);
+
+ // TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+ // Also interpret HOME transient launch as recents
+ if (activity.getActivityType() == ACTIVITY_TYPE_HOME) {
+ mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+ }
}
void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 232c283..907098e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7732,7 +7732,7 @@
final WindowState currentFocus = displayContent.mCurrentFocus;
if (currentFocus != null && currentFocus.mSession.mUid == uid
&& currentFocus.mSession.mPid == pid) {
- return true;
+ return currentFocus.canBeImeTarget();
}
}
return false;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index cd29f0e..6eb2e8a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -57,6 +57,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -817,10 +818,13 @@
return false;
}
- void updateNightModeForAllActivities(int nightMode) {
+ // TODO(b/199277065): Re-assess how app-specific locales are applied based on UXR
+ // TODO(b/199277729): Consider whether we need to add special casing for edge cases like
+ // activity-embeddings etc.
+ void updateAppSpecificSettingsForAllActivities(Integer nightMode, LocaleList localesOverride) {
for (int i = mActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = mActivities.get(i);
- if (r.setOverrideNightMode(nightMode) && r.mVisibleRequested) {
+ if (r.applyAppSpecificConfig(nightMode, localesOverride) && r.mVisibleRequested) {
r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 22db297..2ccbf40 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -372,6 +372,14 @@
private boolean mRedrawForSyncReported;
/**
+ * {@code true} when the client was still drawing for sync when the sync-set was finished or
+ * cancelled. This can happen if the window goes away during a sync. In this situation we need
+ * to make sure to still apply the postDrawTransaction when it finishes to prevent the client
+ * from getting stuck in a bad state.
+ */
+ boolean mClientWasDrawingForSync = false;
+
+ /**
* Special mode that is intended only for the rounded corner overlay: during rotation
* transition, we un-rotate the window token such that the window appears as it did before the
* rotation.
@@ -1980,7 +1988,7 @@
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
- && (atoken == null || atoken.mVisibleRequested)
+ && (atoken == null || atoken.isVisible())
&& !mAnimatingExit && !mDestroying;
}
@@ -2706,6 +2714,13 @@
}
}
+ // Don't allow transient-launch activities to take IME.
+ if (rootTask != null && mActivityRecord != null
+ && mWmService.mAtmService.getTransitionController().isTransientLaunch(
+ mActivityRecord)) {
+ return false;
+ }
+
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
if (!isVisibleOrAdding()) {
@@ -6002,6 +6017,14 @@
return super.isSyncFinished();
}
+ @Override
+ void finishSync(Transaction outMergedTransaction, boolean cancel) {
+ if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
+ mClientWasDrawingForSync = true;
+ }
+ super.finishSync(outMergedTransaction, cancel);
+ }
+
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
if (mOrientationChangeRedrawRequestTime > 0) {
final long duration =
@@ -6017,8 +6040,11 @@
}
executeDrawHandlers(postDrawTransaction);
+
+ final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null;
+ mClientWasDrawingForSync = false;
if (!onSyncFinishedDrawing()) {
- return mWinAnimator.finishDrawingLocked(postDrawTransaction);
+ return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow);
}
if (mActivityRecord != null
@@ -6032,7 +6058,7 @@
mSyncTransaction.merge(postDrawTransaction);
}
- mWinAnimator.finishDrawingLocked(null);
+ mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */);
// We always want to force a traversal after a finish draw for blast sync.
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index f25706a..a0dc247 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -228,7 +228,8 @@
}
}
- boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
+ boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
+ boolean forceApplyNow) {
final boolean startingWindow =
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
if (startingWindow) {
@@ -253,11 +254,11 @@
// If there is no surface, the last draw was for the previous surface. We don't want to
// wait until the new surface is shown and instead just apply the transaction right
// away.
- if (mLastHidden && mDrawState != NO_SURFACE) {
+ if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) {
mPostDrawTransaction.merge(postDrawTransaction);
layoutNeeded = true;
} else {
- postDrawTransaction.apply();
+ mWin.getSyncTransaction().merge(postDrawTransaction);
}
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a94ad4a..bb9740b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,7 +337,7 @@
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
- void setPointerCapture(bool enabled) override;
+ void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -372,8 +372,8 @@
// Show touches feature enable/disable.
bool showTouches;
- // Pointer capture feature enable/disable.
- bool pointerCapture;
+ // The latest request to enable or disable Pointer Capture.
+ PointerCaptureRequest pointerCaptureRequest;
// Sprite controller singleton, created on first use.
sp<SpriteController> spriteController;
@@ -417,7 +417,6 @@
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
- mLocked.pointerCapture = false;
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
@@ -446,7 +445,9 @@
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
- dump += StringPrintf(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
+ dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
+ mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.seq);
}
dump += "\n";
@@ -634,7 +635,7 @@
outConfig->showTouches = mLocked.showTouches;
- outConfig->pointerCapture = mLocked.pointerCapture;
+ outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
outConfig->setDisplayViewports(mLocked.viewports);
@@ -1383,16 +1384,16 @@
checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
}
-void NativeInputManager::setPointerCapture(bool enabled) {
+void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) {
{ // acquire lock
AutoMutex _l(mLock);
- if (mLocked.pointerCapture == enabled) {
+ if (mLocked.pointerCaptureRequest == request) {
return;
}
- ALOGV("%s pointer capture.", enabled ? "Enabling" : "Disabling");
- mLocked.pointerCapture = enabled;
+ ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ mLocked.pointerCaptureRequest = request;
} // release lock
mInputManager->getReader()->requestRefreshConfiguration(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 8ea21ec..a301799 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -46,6 +46,12 @@
@GuardedBy("mLock")
private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+ /** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}.
+ *
+ * <p>For users affiliated with the device, they inherit the policy from {@code DO} so
+ * it will map to the {@code DO}'s policy. Otherwise it will map to the admin of the requesting
+ * user.
+ */
@GuardedBy("mLock")
private final SparseBooleanArray mCanGrantSensorsPermissions = new SparseBooleanArray();
@@ -102,17 +108,16 @@
}
@Override
- public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle) {
+ public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userId) {
synchronized (mLock) {
- return mCanGrantSensorsPermissions.get(userHandle, false);
+ return mCanGrantSensorsPermissions.get(userId, false);
}
}
/** Sets ahmin control over permission grants for user. */
- public void setAdminCanGrantSensorsPermissions(@UserIdInt int userHandle,
- boolean canGrant) {
+ public void setAdminCanGrantSensorsPermissions(@UserIdInt int userId, boolean canGrant) {
synchronized (mLock) {
- mCanGrantSensorsPermissions.put(userHandle, canGrant);
+ mCanGrantSensorsPermissions.put(userId, canGrant);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 70219d2..6b4b0c94 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9144,9 +9144,7 @@
}
/**
- * Returns the ActiveAdmin associated wit the PO or DO on the given user.
- * @param userHandle
- * @return
+ * Returns the ActiveAdmin associated with the PO or DO on the given user.
*/
private @Nullable ActiveAdmin getDeviceOrProfileOwnerAdminLocked(int userHandle) {
ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
@@ -14305,6 +14303,7 @@
maybePauseDeviceWideLoggingLocked();
maybeResumeDeviceWideLoggingLocked();
maybeClearLockTaskPolicyLocked();
+ updateAdminCanGrantSensorsPermissionCache(callingUserId);
}
}
@@ -16968,6 +16967,19 @@
}
@Override
+ public void clearOrganizationIdForUser(int userHandle) {
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle);
+ owner.mOrganizationId = null;
+ owner.mEnrollmentSpecificId = null;
+ saveSettingsLocked(userHandle);
+ }
+ }
+
+ @Override
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams,
@NonNull String callerPackage) {
@@ -17469,7 +17481,10 @@
});
}
- private void setAdminCanGrantSensorsPermissionForUserUnchecked(int userId, boolean canGrant) {
+ private void setAdminCanGrantSensorsPermissionForUserUnchecked(@UserIdInt int userId,
+ boolean canGrant) {
+ Slogf.d(LOG_TAG, "setAdminCanGrantSensorsPermissionForUserUnchecked(%d, %b)",
+ userId, canGrant);
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
@@ -17483,10 +17498,18 @@
}
}
- private void updateAdminCanGrantSensorsPermissionCache(int userId) {
+ private void updateAdminCanGrantSensorsPermissionCache(@UserIdInt int userId) {
synchronized (getLockObject()) {
- ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
- final boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+
+ ActiveAdmin owner;
+ // If the user is affiliated the device (either a DO itself, or an affiliated PO),
+ // use mAdminCanGrantSensorsPermissions from the DO
+ if (isUserAffiliatedWithDeviceLocked(userId)) {
+ owner = getDeviceOwnerAdminLocked();
+ } else {
+ owner = getDeviceOrProfileOwnerAdminLocked(userId);
+ }
+ boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 40a5a81..764f63d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -43,13 +43,17 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.EnterPipRequestedItem;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +65,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
import java.util.ArrayList;
@@ -80,6 +85,9 @@
private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
ArgumentCaptor.forClass(ClientTransaction.class);
+ private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
+ private static final int DEFAULT_USER_ID = 100;
+
@Before
public void setUp() throws Exception {
setBooted(mAtm);
@@ -489,5 +497,269 @@
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
}
+
+ @Test
+ public void testPackageConfigUpdate_locales_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
+
+ WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_nightMode_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpc.getConfiguration().getLocales());
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+ wpc.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME);
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpc.getConfiguration().getLocales());
+
+ packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
+ .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpc.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ mAtm.mInternal.removeUser(DEFAULT_USER_ID);
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ private WindowProcessController createWindowProcessController(String packageName,
+ int userId) {
+ WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
+ ApplicationInfo info = mock(ApplicationInfo.class);
+ info.packageName = packageName;
+ WindowProcessController wpc = new WindowProcessController(
+ mAtm, info, packageName, 0, userId, null, mMockListener);
+ wpc.setThread(mock(IApplicationThread.class));
+ return wpc;
+ }
+
}
+
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d3f2d14..c56b614 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -34,6 +34,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -49,6 +50,7 @@
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
@@ -371,8 +373,9 @@
public void testTopActivityUiModeChangeScheduleConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
activity.mVisibleRequested = true;
- doReturn(true).when(activity).setOverrideNightMode(anyInt());
- mWpc.updateNightModeForAllActivities(Configuration.UI_MODE_NIGHT_YES);
+ doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+ mWpc.updateAppSpecificSettingsForAllActivities(Configuration.UI_MODE_NIGHT_YES,
+ LocaleList.forLanguageTags("en-XA"));
verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
}