Merge "[hbm] Separate HBM times for multi-display devices." into tm-qpr-dev
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9e5e8de..2e3b5d2 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1058,6 +1058,41 @@
public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
/**
+ * This change id excludes the packages it is applied to from the camera compat force rotation
+ * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION =
+ 263959004L; // buganizer id
+
+ /**
+ * This change id excludes the packages it is applied to from activity refresh after camera
+ * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for
+ * context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id
+
+ /**
+ * This change id makes the packages it is applied to do activity refresh after camera compat
+ * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle. See
+ * com.android.server.wm.DisplayRotationCompatPolicy for context.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
+ 264301586L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e1d15de..125bdaf 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -74,6 +74,7 @@
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
+ int getUserSwitchability(int userId);
boolean isUserSwitcherEnabled(int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 414ea83..aa0ac31 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -58,7 +58,6 @@
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.provider.Settings;
-import android.telephony.TelephonyManager;
import android.util.AndroidException;
import android.util.ArraySet;
import android.util.Log;
@@ -1673,7 +1672,7 @@
public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2;
/**
- * Result returned in {@link #getUserSwitchability()} indicating user swichability.
+ * Result returned in {@link #getUserSwitchability()} indicating user switchability.
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -2040,25 +2039,16 @@
* @hide
*/
@Deprecated
- @RequiresPermission(allOf = {
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead.
- conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@UserHandleAware
public boolean canSwitchUsers() {
- boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
- boolean inCall = false;
- TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
- if (telephonyManager != null) {
- inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+ try {
+ return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
- boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
- && !isUserSwitchDisallowed;
}
/**
@@ -2092,34 +2082,14 @@
* @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
* @hide
*/
- @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
- android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
- final TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
- int flags = SWITCHABILITY_STATUS_OK;
- if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
- flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
+ try {
+ return mService.getUserSwitchability(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
- if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
- flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
- }
-
- // System User is always unlocked in Headless System User Mode, so ignore this flag
- if (!isHeadlessSystemUserMode()) {
- final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
- final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-
- if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
- flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
- }
- }
-
- return flags;
}
/**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 4b25c88..182a497 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -52,7 +52,7 @@
/** @hide */
@StringDef (prefix = { "KEY_" }, value = {
KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA,
- KEY_TEXT_REPLIES, KEY_USER_SENTIMENT
+ KEY_TEXT_REPLIES, KEY_USER_SENTIMENT, KEY_IMPORTANCE_PROPOSAL
})
@Retention(RetentionPolicy.SOURCE)
public @interface Keys {}
@@ -122,6 +122,19 @@
public static final String KEY_IMPORTANCE = "key_importance";
/**
+ * Weaker than {@link #KEY_IMPORTANCE}, this adjustment suggests an importance rather than
+ * mandates an importance change.
+ *
+ * A notification listener can interpet this suggestion to show the user a prompt to change
+ * notification importance for the notification (or type, or app) moving forward.
+ *
+ * Data type: int, one of importance values e.g.
+ * {@link android.app.NotificationManager#IMPORTANCE_MIN}.
+ * @hide
+ */
+ public static final String KEY_IMPORTANCE_PROPOSAL = "key_importance_proposal";
+
+ /**
* Data type: float, a ranking score from 0 (lowest) to 1 (highest).
* Used to rank notifications inside that fall under the same classification (i.e. alerting,
* silenced).
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ad2e9d5..dc4cb9f 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1711,6 +1711,8 @@
private ShortcutInfo mShortcutInfo;
private @RankingAdjustment int mRankingAdjustment;
private boolean mIsBubble;
+ // Notification assistant importance suggestion
+ private int mProposedImportance;
private static final int PARCEL_VERSION = 2;
@@ -1748,6 +1750,7 @@
out.writeParcelable(mShortcutInfo, flags);
out.writeInt(mRankingAdjustment);
out.writeBoolean(mIsBubble);
+ out.writeInt(mProposedImportance);
}
/** @hide */
@@ -1786,6 +1789,7 @@
mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
mRankingAdjustment = in.readInt();
mIsBubble = in.readBoolean();
+ mProposedImportance = in.readInt();
}
@@ -1878,6 +1882,22 @@
}
/**
+ * Returns the proposed importance provided by the {@link NotificationAssistantService}.
+ *
+ * This can be used to suggest that the user change the importance of this type of
+ * notification moving forward. A value of
+ * {@link NotificationManager#IMPORTANCE_UNSPECIFIED} means that the NAS has not recommended
+ * a change to the importance, and no UI should be shown to the user. See
+ * {@link Adjustment#KEY_IMPORTANCE_PROPOSAL}.
+ *
+ * @return the importance of the notification
+ * @hide
+ */
+ public @NotificationManager.Importance int getProposedImportance() {
+ return mProposedImportance;
+ }
+
+ /**
* If the system has overridden the group key, then this will be non-null, and this
* key should be used to bundle notifications.
*/
@@ -2041,7 +2061,7 @@
boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies, boolean canBubble,
boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
- int rankingAdjustment, boolean isBubble) {
+ int rankingAdjustment, boolean isBubble, int proposedImportance) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -2067,6 +2087,7 @@
mShortcutInfo = shortcutInfo;
mRankingAdjustment = rankingAdjustment;
mIsBubble = isBubble;
+ mProposedImportance = proposedImportance;
}
/**
@@ -2107,7 +2128,8 @@
other.mIsConversation,
other.mShortcutInfo,
other.mRankingAdjustment,
- other.mIsBubble);
+ other.mIsBubble,
+ other.mProposedImportance);
}
/**
@@ -2166,7 +2188,8 @@
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
(other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
&& Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
- && Objects.equals(mIsBubble, other.mIsBubble);
+ && Objects.equals(mIsBubble, other.mIsBubble)
+ && Objects.equals(mProposedImportance, other.mProposedImportance);
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ed9cb00..abc4926 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -853,6 +853,143 @@
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
/**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be excluded from the
+ * camera compatibility force rotation treatment.
+ *
+ * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+ * for more details).
+ *
+ * <p>With this property set to {@code true} or unset, the system may apply the force rotation
+ * treatment to fixed orientation activities. Device manufacturers can exclude packages from the
+ * treatment using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code false}, the system will not apply the force rotation
+ * treatment.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
+ "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
+
+ /**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be excluded
+ * from the activity "refresh" after the camera compatibility force rotation treatment.
+ *
+ * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
+ * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context).
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ *
+ * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+ * for more details).
+ *
+ * <p>With this property set to {@code true} or unset, the system may "refresh" activity after
+ * the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
+ * using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code false}, the system will not "refresh" activity after the
+ * force rotation treatment.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
+ "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
+
+ /**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity should be or shouldn't be
+ * "refreshed" after the camera compatibility force rotation treatment using "paused ->
+ * resumed" cycle rather than "stopped -> resumed".
+ *
+ * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
+ * orientation of the device and set opposite to natural orientation for a landscape app
+ * window. Mismatch between them can lead to camera issues like sideways or stretched
+ * viewfinder since this is one of the strongest assumptions that apps make when they implement
+ * camera previews. Since app and natural display orientations aren't guaranteed to match, the
+ * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+ * camera and is removed once camera is closed.
+ *
+ * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
+ * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
+ * (if overridden by device manufacturers or using this property). This allows to clear cached
+ * values in apps (e.g., display or camera rotation) that influence camera preview and can lead
+ * to sideways or stretching issues persisting even after force rotation.
+ *
+ * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
+ * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+ * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+ * for more details).
+ *
+ * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
+ * cycle using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code true}, the system will "refresh" activity after the
+ * force rotation treatment using "resumed -> paused -> resumed" cycle.
+ *
+ * <p>With this property set to {@code false}, the system will not "refresh" activity after the
+ * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device
+ * manufacturer adds the corresponding override.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
+ "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 1024e2e..4a4f561 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -18,10 +18,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -52,8 +50,6 @@
@SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackEvent} instance.
@@ -62,16 +58,12 @@
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing application
- * window.
*/
- public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
}
private BackEvent(@NonNull Parcel in) {
@@ -79,7 +71,6 @@
mTouchY = in.readFloat();
mProgress = in.readFloat();
mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
@@ -105,7 +96,6 @@
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
@@ -136,16 +126,6 @@
return mSwipeEdge;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackEvent{"
@@ -153,7 +133,6 @@
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ ", mSwipeEdge" + mSwipeEdge
- + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
}
}
diff --git a/core/java/android/window/BackMotionEvent.aidl b/core/java/android/window/BackMotionEvent.aidl
new file mode 100644
index 0000000..7c675c3
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+parcelable BackMotionEvent;
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
new file mode 100644
index 0000000..8012a1c
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+/**
+ * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus
+ * any {@link RemoteAnimationTarget} the gesture manipulates.
+ *
+ * @see BackEvent
+ * @hide
+ */
+public final class BackMotionEvent implements Parcelable {
+ private final float mTouchX;
+ private final float mTouchY;
+ private final float mProgress;
+
+ @BackEvent.SwipeEdge
+ private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+ /**
+ * Creates a new {@link BackMotionEvent} instance.
+ *
+ * @param touchX Absolute X location of the touch point of this event.
+ * @param touchY Absolute Y location of the touch point of this event.
+ * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing
+ * application window.
+ */
+ public BackMotionEvent(float touchX, float touchY, float progress,
+ @BackEvent.SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ mTouchX = touchX;
+ mTouchY = touchY;
+ mProgress = progress;
+ mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
+ }
+
+ private BackMotionEvent(@NonNull Parcel in) {
+ mTouchX = in.readFloat();
+ mTouchY = in.readFloat();
+ mProgress = in.readFloat();
+ mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+ }
+
+ @NonNull
+ public static final Creator<BackMotionEvent> CREATOR = new Creator<BackMotionEvent>() {
+ @Override
+ public BackMotionEvent createFromParcel(Parcel in) {
+ return new BackMotionEvent(in);
+ }
+
+ @Override
+ public BackMotionEvent[] newArray(int size) {
+ return new BackMotionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(mTouchX);
+ dest.writeFloat(mTouchY);
+ dest.writeFloat(mProgress);
+ dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
+ }
+
+ /**
+ * Returns the progress of a {@link BackEvent}.
+ *
+ * @see BackEvent#getProgress()
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Returns the absolute X location of the touch point.
+ */
+ public float getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public float getTouchY() {
+ return mTouchY;
+ }
+
+ /**
+ * Returns the screen edge that the swipe starts from.
+ */
+ @BackEvent.SwipeEdge
+ public int getSwipeEdge() {
+ return mSwipeEdge;
+ }
+
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
+ @Override
+ public String toString() {
+ return "BackMotionEvent{"
+ + "mTouchX=" + mTouchX
+ + ", mTouchY=" + mTouchY
+ + ", mProgress=" + mProgress
+ + ", mSwipeEdge" + mSwipeEdge
+ + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ + "}";
+ }
+}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index dd4385c..38c52e7 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -40,7 +40,7 @@
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
- private BackEvent mLastBackEvent;
+ private BackMotionEvent mLastBackEvent;
private boolean mStarted = false;
private void setProgress(float progress) {
@@ -82,9 +82,9 @@
/**
* Sets a new target position for the back progress.
*
- * @param event the {@link BackEvent} containing the latest target progress.
+ * @param event the {@link BackMotionEvent} containing the latest target progress.
*/
- public void onBackProgressed(BackEvent event) {
+ public void onBackProgressed(BackMotionEvent event) {
if (!mStarted) {
return;
}
@@ -95,11 +95,11 @@
/**
* Starts the back progress animation.
*
- * @param event the {@link BackEvent} that started the gesture.
+ * @param event the {@link BackMotionEvent} that started the gesture.
* @param callback the back callback to invoke for the gesture. It will receive back progress
* dispatches as the progress animation updates.
*/
- public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
reset();
mLastBackEvent = event;
mCallback = callback;
@@ -129,8 +129,7 @@
}
mCallback.onProgressUpdate(
new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
- progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
- mLastBackEvent.getDepartingAnimationTarget()));
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
}
}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 6af8ddd..159c0e8 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,7 +17,7 @@
package android.window;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
@@ -30,18 +30,19 @@
* Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the touch or button press.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch
+ * or button press.
*/
- void onBackStarted(in BackEvent backEvent);
+ void onBackStarted(in BackMotionEvent backMotionEvent);
/**
* Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest
+ * touch point and the progress that the back animation should seek to.
*/
- void onBackProgressed(in BackEvent backEvent);
+ void onBackProgressed(in BackMotionEvent backMotionEvent);
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index fda39c1..dd9483a 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -229,19 +229,21 @@
}
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
mProgressAnimator.onBackStarted(backEvent, event ->
callback.onBackProgressed(event));
- callback.onBackStarted(backEvent);
+ callback.onBackStarted(new BackEvent(
+ backEvent.getTouchX(), backEvent.getTouchY(),
+ backEvent.getProgress(), backEvent.getSwipeEdge()));
}
});
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f370ebd..9d6b29e 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -17,6 +17,7 @@
package android.window;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -60,8 +61,8 @@
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
- @Mock
- private BackEvent mBackEvent;
+ private final BackMotionEvent mBackEvent = new BackMotionEvent(
+ 0, 0, 0, BackEvent.EDGE_LEFT, null);
@Before
public void setUp() throws Exception {
@@ -89,12 +90,12 @@
captor.capture());
captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
verifyNoMoreInteractions(mCallback1);
}
@@ -114,7 +115,7 @@
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
}
@Test
@@ -152,6 +153,6 @@
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
}
}
diff --git a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
index 0bcaa53..b7ff96e 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@color/decor_button_dark_color">
<path
android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/>
</vector>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index cbcd949..2363092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -51,6 +51,7 @@
import android.view.SurfaceControl;
import android.window.BackAnimationAdaptor;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationRunner;
import android.window.IBackNaviAnimationController;
@@ -173,11 +174,11 @@
boolean consumed = false;
if (mWaitingAnimation && mOnBackCallback != null) {
if (mTriggerBack) {
- final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(1);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackInvoked(mOnBackCallback);
} else {
- final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(0);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackCancelled(mOnBackCallback);
}
@@ -480,7 +481,7 @@
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
- final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
} else if (mEnableAnimations.get()) {
@@ -573,7 +574,7 @@
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -611,7 +612,7 @@
}
private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -730,7 +731,7 @@
}
dispatchOnBackStarted(mBackToLauncherCallback,
mTouchTracker.createStartEvent(mAnimationTarget));
- final BackEvent backInit = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backInit = mTouchTracker.createProgressEvent();
if (!mCachingBackDispatcher.consume()) {
dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index ccfac65..695ef4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -19,6 +19,7 @@
import android.os.SystemProperties;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Helper class to record the touch location for gesture and generate back events.
@@ -82,11 +83,11 @@
mSwipeEdge = BackEvent.EDGE_LEFT;
}
- BackEvent createStartEvent(RemoteAnimationTarget target) {
- return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
}
- BackEvent createProgressEvent() {
+ BackMotionEvent createProgressEvent() {
float progressThreshold = PROGRESS_THRESHOLD >= 0
? PROGRESS_THRESHOLD : mProgressThreshold;
progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
@@ -109,8 +110,8 @@
return createProgressEvent(progress);
}
- BackEvent createProgressEvent(float progress) {
- return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ BackMotionEvent createProgressEvent(float progress) {
+ return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
}
public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index f5f3573..63b03ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -251,7 +251,8 @@
* Show apps on desktop
*/
void showDesktopApps() {
- WindowContainerTransaction wct = bringDesktopAppsToFront();
+ // Bring apps to front, ignoring their visibility status to always ensure they are on top.
+ WindowContainerTransaction wct = bringDesktopAppsToFront(true /* ignoreVisibility */);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
@@ -261,7 +262,7 @@
}
@NonNull
- private WindowContainerTransaction bringDesktopAppsToFront() {
+ private WindowContainerTransaction bringDesktopAppsToFront(boolean force) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
@@ -278,12 +279,14 @@
return wct;
}
- final boolean allActiveTasksAreVisible = taskInfos.stream()
- .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
- if (allActiveTasksAreVisible) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: active tasks are already in front, skipping.");
- return wct;
+ if (!force) {
+ final boolean allActiveTasksAreVisible = taskInfos.stream()
+ .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+ if (allActiveTasksAreVisible) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+ return wct;
+ }
}
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"bringDesktopAppsToFront: reordering all active tasks to the front");
@@ -354,7 +357,7 @@
if (wct == null) {
wct = new WindowContainerTransaction();
}
- wct.merge(bringDesktopAppsToFront(), true /* transfer */);
+ wct.merge(bringDesktopAppsToFront(false /* ignoreVisibility */), true /* transfer */);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
return wct;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 3341470..9165f70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -84,8 +84,7 @@
fun showDesktopApps() {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
val wct = WindowContainerTransaction()
-
- bringDesktopAppsToFront(wct)
+ bringDesktopAppsToFront(wct, force = true)
// Execute transaction if there are pending operations
if (!wct.isEmpty) {
@@ -150,11 +149,11 @@
?: WINDOWING_MODE_UNDEFINED
}
- private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
+ private fun bringDesktopAppsToFront(wct: WindowContainerTransaction, force: Boolean = false) {
val activeTasks = desktopModeTaskRepository.getActiveTasks()
// Skip if all tasks are already visible
- if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
+ if (!force && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"bringDesktopAppsToFront: active tasks are already in front, skipping."
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 2e328b0..2754496 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -53,6 +53,7 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackNaviAnimationController;
import android.window.IOnBackInvokedCallback;
@@ -246,10 +247,11 @@
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackMotionEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -271,17 +273,18 @@
RemoteAnimationTarget animationTarget = createAnimationTarget();
IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false);
triggerBackGesture();
- verify(appCallback, never()).onBackStarted(any(BackEvent.class));
+ verify(appCallback, never()).onBackStarted(any(BackMotionEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackMotionEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
}
@@ -314,7 +317,7 @@
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
}
@Test
@@ -333,7 +336,7 @@
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
}
@@ -349,7 +352,7 @@
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index 3aefc3f..ba9c159 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +39,7 @@
@Test
public void generatesProgress_onStart() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
- BackEvent event = mTouchTracker.createStartEvent(null);
+ BackMotionEvent event = mTouchTracker.createStartEvent(null);
assertEquals(event.getProgress(), 0f, 0f);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 08af3d3..35cc168 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -279,7 +279,7 @@
}
@Test
- public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
final RunningTaskInfo task1 = createFreeformTask();
mDesktopModeTaskRepository.addActiveTask(task1.taskId);
mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
@@ -294,8 +294,17 @@
mController.showDesktopApps();
final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // No reordering needed.
- assertThat(wct.getHierarchyOps()).isEmpty();
+ // Check wct has reorder calls
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9a92879..4011d08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -150,8 +150,8 @@
}
@Test
- fun showDesktopApps_appsAlreadyVisible_doesNothing() {
- setUpHomeTask()
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront() {
+ val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -159,7 +159,12 @@
controller.showDesktopApps()
- verifyWCTNotExecuted()
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
}
@Test
@@ -207,6 +212,23 @@
}
@Test
+ fun moveToDesktop_otherFreeformTasksBroughtToFront() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveToDesktop(fullscreenTask)
+
+ with(getLatestWct()) {
+ assertThat(hierarchyOps).hasSize(3)
+ assertReorderSequence(homeTask, freeformTask, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
fun moveToFullscreen() {
val task = setUpFreeformTask()
controller.moveToFullscreen(task)
@@ -406,3 +428,9 @@
assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
assertThat(op.container).isEqualTo(task.token.asBinder())
}
+
+private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) {
+ for (i in tasks.indices) {
+ assertReorderAt(i, tasks[i])
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index fe349f2..8f70dcc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -37,6 +37,8 @@
import android.view.WindowManager
import android.view.animation.Interpolator
import android.view.animation.PathInterpolator
+import androidx.annotation.BinderThread
+import androidx.annotation.UiThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import kotlin.math.roundToInt
@@ -226,7 +228,7 @@
// If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started.
if (willAnimate) {
- runner.postTimeout()
+ runner.delegate.postTimeout()
// Hide the keyguard using the launch animation instead of the default unlock animation.
if (hideKeyguardWithAnimation) {
@@ -389,14 +391,51 @@
fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
}
- class Runner(
+ @VisibleForTesting
+ inner class Runner(
+ controller: Controller,
+ callback: Callback,
+ /** The animator to use to animate the window launch. */
+ launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+ /** Listener for animation lifecycle events. */
+ listener: Listener? = null
+ ) : IRemoteAnimationRunner.Stub() {
+ private val context = controller.launchContainer.context
+ internal val delegate: AnimationDelegate
+
+ init {
+ delegate = AnimationDelegate(controller, callback, launchAnimator, listener)
+ }
+
+ @BinderThread
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<out RemoteAnimationTarget>?,
+ wallpapers: Array<out RemoteAnimationTarget>?,
+ nonApps: Array<out RemoteAnimationTarget>?,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ context.mainExecutor.execute {
+ delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
+ }
+ }
+
+ @BinderThread
+ override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
+ context.mainExecutor.execute { delegate.onAnimationCancelled(isKeyguardOccluded) }
+ }
+ }
+
+ class AnimationDelegate
+ @JvmOverloads
+ constructor(
private val controller: Controller,
private val callback: Callback,
/** The animator to use to animate the window launch. */
private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
/** Listener for animation lifecycle events. */
private val listener: Listener? = null
- ) : IRemoteAnimationRunner.Stub() {
+ ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
private val transactionApplierView =
@@ -419,6 +458,7 @@
// posting it.
private var onTimeout = Runnable { onAnimationTimedOut() }
+ @UiThread
internal fun postTimeout() {
launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT)
}
@@ -427,19 +467,20 @@
launchContainer.removeCallbacks(onTimeout)
}
+ @UiThread
override fun onAnimationStart(
@WindowManager.TransitionOldType transit: Int,
apps: Array<out RemoteAnimationTarget>?,
wallpapers: Array<out RemoteAnimationTarget>?,
nonApps: Array<out RemoteAnimationTarget>?,
- iCallback: IRemoteAnimationFinishedCallback?
+ callback: IRemoteAnimationFinishedCallback?
) {
removeTimeout()
// The animation was started too late and we already notified the controller that it
// timed out.
if (timedOut) {
- iCallback?.invoke()
+ callback?.invoke()
return
}
@@ -449,7 +490,7 @@
return
}
- context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) }
+ startAnimation(apps, nonApps, callback)
}
private fun startAnimation(
@@ -687,6 +728,7 @@
controller.onLaunchAnimationCancelled()
}
+ @UiThread
override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
if (timedOut) {
return
@@ -695,10 +737,9 @@
Log.i(TAG, "Remote animation was cancelled")
cancelled = true
removeTimeout()
- context.mainExecutor.execute {
- animation?.cancel()
- controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded)
- }
+
+ animation?.cancel()
+ controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded)
}
private fun IRemoteAnimationFinishedCallback.invoke() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
new file mode 100644
index 0000000..337408b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
@@ -0,0 +1,30 @@
+package com.android.systemui.animation
+
+import android.annotation.UiThread
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+
+/**
+ * A component capable of running remote animations.
+ *
+ * Expands the IRemoteAnimationRunner API by allowing for different types of more specialized
+ * callbacks.
+ */
+interface RemoteAnimationDelegate<in T : IRemoteAnimationFinishedCallback> {
+ /**
+ * Called on the UI thread when the animation targets are received. Sets up and kicks off the
+ * animation.
+ */
+ @UiThread
+ fun onAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<out RemoteAnimationTarget>?,
+ wallpapers: Array<out RemoteAnimationTarget>?,
+ nonApps: Array<out RemoteAnimationTarget>?,
+ callback: T?
+ )
+
+ /** Called on the UI thread when a signal is received to cancel the animation. */
+ @UiThread fun onAnimationCancelled(isKeyguardOccluded: Boolean)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 462b90a..86bd5f2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -54,7 +54,6 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- var tag: String = "UnnamedClockView"
var logBuffer: LogBuffer? = null
private val time = Calendar.getInstance()
@@ -132,7 +131,7 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- logBuffer?.log(tag, DEBUG, "onAttachedToWindow")
+ logBuffer?.log(TAG, DEBUG, "onAttachedToWindow")
refreshFormat()
}
@@ -148,7 +147,7 @@
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = formattedText?.toString() },
{ "refreshTime: new formattedText=$str1" }
)
@@ -157,7 +156,7 @@
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = formattedText?.toString() },
{ "refreshTime: done setting new time text to: $str1" }
)
@@ -167,17 +166,17 @@
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
- logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout")
+ logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout")
}
requestLayout()
- logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout")
+ logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
time.timeZone = timeZone
refreshFormat()
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = timeZone?.toString() },
{ "onTimeZoneChanged newTimeZone=$str1" }
)
@@ -194,7 +193,7 @@
} else {
animator.updateLayout(layout)
}
- logBuffer?.log(tag, DEBUG, "onMeasure")
+ logBuffer?.log(TAG, DEBUG, "onMeasure")
}
override fun onDraw(canvas: Canvas) {
@@ -206,12 +205,12 @@
} else {
super.onDraw(canvas)
}
- logBuffer?.log(tag, DEBUG, "onDraw lastDraw")
+ logBuffer?.log(TAG, DEBUG, "onDraw")
}
override fun invalidate() {
super.invalidate()
- logBuffer?.log(tag, DEBUG, "invalidate")
+ logBuffer?.log(TAG, DEBUG, "invalidate")
}
override fun onTextChanged(
@@ -221,7 +220,7 @@
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = text.toString() },
{ "onTextChanged text=$str1" }
)
@@ -238,7 +237,7 @@
}
fun animateColorChange() {
- logBuffer?.log(tag, DEBUG, "animateColorChange")
+ logBuffer?.log(TAG, DEBUG, "animateColorChange")
setTextStyle(
weight = lockScreenWeight,
textSize = -1f,
@@ -260,7 +259,7 @@
}
fun animateAppearOnLockscreen() {
- logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
+ logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen")
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -285,7 +284,7 @@
if (isAnimationEnabled && textAnimator == null) {
return
}
- logBuffer?.log(tag, DEBUG, "animateFoldAppear")
+ logBuffer?.log(TAG, DEBUG, "animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -312,7 +311,7 @@
// Skip charge animation if dozing animation is already playing.
return
}
- logBuffer?.log(tag, DEBUG, "animateCharge")
+ logBuffer?.log(TAG, DEBUG, "animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -336,7 +335,7 @@
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- logBuffer?.log(tag, DEBUG, "animateDoze")
+ logBuffer?.log(TAG, DEBUG, "animateDoze")
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -455,7 +454,7 @@
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
- logBuffer?.log(tag, DEBUG,
+ logBuffer?.log(TAG, DEBUG,
{ str1 = format?.toString() },
{ "refreshFormat format=$str1" }
)
@@ -466,6 +465,7 @@
fun dump(pw: PrintWriter) {
pw.println("$this")
+ pw.println(" alpha=$alpha")
pw.println(" measuredWidth=$measuredWidth")
pw.println(" measuredHeight=$measuredHeight")
pw.println(" singleLineInternal=$isSingleLineInternal")
@@ -626,7 +626,7 @@
}
companion object {
- private val TAG = AnimatableClockView::class.simpleName
+ private val TAG = AnimatableClockView::class.simpleName!!
const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e138ef8..7645dec 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -88,13 +88,6 @@
events.onTimeTick()
}
- override fun setLogBuffer(logBuffer: LogBuffer) {
- smallClock.view.tag = "smallClockView"
- largeClock.view.tag = "largeClockView"
- smallClock.view.logBuffer = logBuffer
- largeClock.view.logBuffer = logBuffer
- }
-
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
) : ClockFaceController {
@@ -104,6 +97,12 @@
private var isRegionDark = false
protected var targetRegion: Rect? = null
+ override var logBuffer: LogBuffer?
+ get() = view.logBuffer
+ set(value) {
+ view.logBuffer = value
+ }
+
init {
view.setColors(currentColor, currentColor)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 66e44b9..a2a0709 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -71,9 +71,6 @@
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) {}
-
- /** Optional method for debug logging */
- fun setLogBuffer(logBuffer: LogBuffer) {}
}
/** Interface for a specific clock face version rendered by the clock */
@@ -83,6 +80,9 @@
/** Events specific to this clock face */
val events: ClockFaceEvents
+
+ /** Some clocks may log debug information */
+ var logBuffer: LogBuffer?
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 6436dcb..e99b214 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -159,8 +159,13 @@
* bug report more actionable, so using the [log] with a messagePrinter to add more detail to
* every log may do more to improve overall logging than adding more logs with this method.
*/
- fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
- log(tag, level, { str1 = message }, { str1!! })
+ @JvmOverloads
+ fun log(
+ tag: String,
+ level: LogLevel,
+ @CompileTimeConstant message: String,
+ exception: Throwable? = null,
+ ) = log(tag, level, { str1 = message }, { str1!! }, exception)
/**
* You should call [log] instead of this method.
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index f96644f..030eaa6 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -16,6 +16,18 @@
public <init>();
}
+# Needed to ensure callback field references are kept in their respective
+# owning classes when the downstream callback registrars only store weak refs.
+# TODO(b/264686688): Handle these cases with more targeted annotations.
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ private com.android.keyguard.KeyguardUpdateMonitorCallback *;
+ private com.android.systemui.privacy.PrivacyItemController$Callback *;
+ private com.android.systemui.settings.UserTracker$Callback *;
+ private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
+ private com.android.systemui.util.service.Observer$Callback *;
+ private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
+}
+
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index db338b6..87361b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2366,6 +2366,8 @@
<string name="media_transfer_failed">Something went wrong. Try again.</string>
<!-- Text to indicate that a media transfer is currently in-progress, aka loading. [CHAR LIMIT=NONE] -->
<string name="media_transfer_loading">Loading</string>
+ <!-- Default name of the device. [CHAR LIMIT=30] -->
+ <string name="media_ttt_default_device_type">tablet</string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8f38e58..a45ce42 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -38,9 +38,11 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.dagger.KeyguardClockLog
+import com.android.systemui.log.dagger.KeyguardSmallClockLog
+import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -73,16 +75,18 @@
private val context: Context,
@Main private val mainExecutor: Executor,
@Background private val bgExecutor: Executor,
- @KeyguardClockLog private val logBuffer: LogBuffer?,
+ @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
+ @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
private val featureFlags: FeatureFlags
) {
var clock: ClockController? = null
set(value) {
field = value
if (value != null) {
- if (logBuffer != null) {
- value.setLogBuffer(logBuffer)
- }
+ smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
+ value.smallClock.logBuffer = smallLogBuffer
+ largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
+ value.largeClock.logBuffer = largeLogBuffer
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
@@ -325,4 +329,8 @@
}
}
}
+
+ companion object {
+ private val TAG = ClockEventController::class.simpleName!!
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 62babad..4acbb0a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -7,7 +7,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -20,11 +19,15 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import kotlin.Unit;
+
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -87,6 +90,7 @@
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@VisibleForTesting boolean mAnimateOnLayout = true;
+ private LogBuffer mLogBuffer = null;
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -113,6 +117,14 @@
onDensityOrFontScaleChanged();
}
+ public void setLogBuffer(LogBuffer logBuffer) {
+ mLogBuffer = logBuffer;
+ }
+
+ public LogBuffer getLogBuffer() {
+ return mLogBuffer;
+ }
+
void setClock(ClockController clock, int statusBarState) {
mClock = clock;
@@ -121,12 +133,16 @@
mLargeClockFrame.removeAllViews();
if (clock == null) {
- Log.e(TAG, "No clock being shown");
+ if (mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.ERROR, "No clock being shown");
+ }
return;
}
// Attach small and big clock views to hierarchy.
- Log.i(TAG, "Attached new clock views to switch");
+ if (mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.INFO, "Attached new clock views to switch");
+ }
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
updateClockTargetRegions();
@@ -152,8 +168,18 @@
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
- Log.i(TAG, "updateClockViews; useLargeClock=" + useLargeClock + "; animate=" + animate
- + "; mChildrenAreLaidOut=" + mChildrenAreLaidOut);
+ if (mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.DEBUG, (msg) -> {
+ msg.setBool1(useLargeClock);
+ msg.setBool2(animate);
+ msg.setBool3(mChildrenAreLaidOut);
+ return Unit.INSTANCE;
+ }, (msg) -> "updateClockViews"
+ + "; useLargeClock=" + msg.getBool1()
+ + "; animate=" + msg.getBool2()
+ + "; mChildrenAreLaidOut=" + msg.getBool3());
+ }
+
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
@@ -183,6 +209,7 @@
if (!animate) {
out.setAlpha(0f);
+ out.setVisibility(INVISIBLE);
in.setAlpha(1f);
in.setVisibility(VISIBLE);
mStatusArea.setTranslationY(statusAreaYTranslation);
@@ -198,7 +225,10 @@
direction * -mClockSwitchYAmount));
mClockOutAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- mClockOutAnim = null;
+ if (mClockOutAnim == animation) {
+ out.setVisibility(INVISIBLE);
+ mClockOutAnim = null;
+ }
}
});
@@ -212,7 +242,9 @@
mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
mClockInAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- mClockInAnim = null;
+ if (mClockInAnim == animation) {
+ mClockInAnim = null;
+ }
}
});
@@ -225,7 +257,9 @@
mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- mStatusAreaAnim = null;
+ if (mStatusAreaAnim == animation) {
+ mStatusAreaAnim = null;
+ }
}
});
mStatusAreaAnim.start();
@@ -269,7 +303,9 @@
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
pw.println(" mSmallClockFrame: " + mSmallClockFrame);
+ pw.println(" mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha());
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
+ pw.println(" mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha());
pw.println(" mStatusArea: " + mStatusArea);
pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 788f120..88ce2a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -38,8 +38,11 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
@@ -62,6 +65,8 @@
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
+ private static final String TAG = "KeyguardClockSwitchController";
+
private final StatusBarStateController mStatusBarStateController;
private final ClockRegistry mClockRegistry;
private final KeyguardSliceViewController mKeyguardSliceViewController;
@@ -70,6 +75,7 @@
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
+ private final LogBuffer mLogBuffer;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -119,7 +125,8 @@
SecureSettings secureSettings,
@Main Executor uiExecutor,
DumpManager dumpManager,
- ClockEventController clockEventController) {
+ ClockEventController clockEventController,
+ @KeyguardClockLog LogBuffer logBuffer) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
@@ -131,6 +138,8 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
mClockEventController = clockEventController;
+ mLogBuffer = logBuffer;
+ mView.setLogBuffer(mLogBuffer);
mClockChangedListener = () -> {
setClock(mClockRegistry.createCurrentClock());
@@ -337,10 +346,6 @@
int clockHeight = clock.getLargeClock().getView().getHeight();
return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
} else {
- // This is only called if we've never shown the large clock as the frame is inflated
- // with 'gone', but then the visibility is never set when it is animated away by
- // KeyguardClockSwitch, instead it is removed from the view hierarchy.
- // TODO(b/261755021): Cleanup Large Frame Visibility
int clockHeight = clock.getSmallClock().getView().getHeight();
return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
}
@@ -358,15 +363,11 @@
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
return clock.getLargeClock().getView().getHeight();
} else {
- // Is not called except in certain edge cases, see comment in getClockBottom
- // TODO(b/261755021): Cleanup Large Frame Visibility
return clock.getSmallClock().getView().getHeight();
}
}
boolean isClockTopAligned() {
- // Returns false except certain edge cases, see comment in getClockBottom
- // TODO(b/261755021): Cleanup Large Frame Visibility
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
@@ -378,6 +379,10 @@
}
private void setClock(ClockController clock) {
+ if (clock != null && mLogBuffer != null) {
+ mLogBuffer.log(TAG, LogLevel.INFO, "New Clock");
+ }
+
mClockEventController.setClock(clock);
mView.setClock(clock, mStatusBarStateController.getState());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index b106fec..2c7eceb 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -17,36 +17,46 @@
package com.android.keyguard.logging
import com.android.systemui.log.dagger.KeyguardLog
-import com.android.systemui.plugins.log.ConstantStringsLogger
-import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
-private const val TAG = "KeyguardLog"
+private const val BIO_TAG = "KeyguardLog"
/**
* Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
* temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
* an overkill.
*/
-class KeyguardLogger @Inject constructor(@KeyguardLog val buffer: LogBuffer) :
- ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
+class KeyguardLogger
+@Inject
+constructor(
+ @KeyguardLog val buffer: LogBuffer,
+) {
+ @JvmOverloads
+ fun log(
+ tag: String,
+ level: LogLevel,
+ @CompileTimeConstant msg: String,
+ ex: Throwable? = null,
+ ) = buffer.log(tag, level, msg, ex)
- fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
- buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
- }
-
- fun v(msg: String, arg: Any) {
- buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" })
- }
-
- fun i(msg: String, arg: Any) {
- buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+ fun log(
+ tag: String,
+ level: LogLevel,
+ @CompileTimeConstant msg: String,
+ arg: Any,
+ ) {
+ buffer.log(
+ tag,
+ level,
+ {
+ str1 = msg
+ str2 = arg.toString()
+ },
+ { "$str1: $str2" }
+ )
}
@JvmOverloads
@@ -56,8 +66,8 @@
msg: String? = null
) {
buffer.log(
- TAG,
- DEBUG,
+ BIO_TAG,
+ LogLevel.DEBUG,
{
str1 = context
str2 = "$msgId"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index bf85b36..ae8caad 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -431,9 +431,6 @@
unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
// 1300 - screenshots
- // TODO(b/254512719): Tracking Bug
- @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
-
// TODO(b/254513155): Tracking Bug
@JvmField
val SCREENSHOT_WORK_PROFILE_POLICY =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index a2661d7..d4e23499 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,11 +19,14 @@
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
+private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
+
/** Collect flows of interest for auditing keyguard transitions. */
@SysUISingleton
class KeyguardTransitionAuditLogger
@@ -37,35 +40,47 @@
fun start() {
scope.launch {
- keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
+ keyguardInteractor.wakefulnessModel.collect {
+ logger.log(TAG, VERBOSE, "WakefulnessModel", it)
+ }
}
scope.launch {
- keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) }
+ keyguardInteractor.isBouncerShowing.collect {
+ logger.log(TAG, VERBOSE, "Bouncer showing", it)
+ }
}
- scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+ scope.launch {
+ keyguardInteractor.isDozing.collect { logger.log(TAG, VERBOSE, "isDozing", it) }
+ }
- scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } }
+ scope.launch {
+ keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) }
+ }
scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
- logger.i("Finished transition", it)
+ logger.log(TAG, VERBOSE, "Finished transition", it)
}
}
scope.launch {
interactor.canceledKeyguardTransitionStep.collect {
- logger.i("Canceled transition", it)
+ logger.log(TAG, VERBOSE, "Canceled transition", it)
}
}
scope.launch {
- interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
+ interactor.startedKeyguardTransitionStep.collect {
+ logger.log(TAG, VERBOSE, "Started transition", it)
+ }
}
scope.launch {
- keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) }
+ keyguardInteractor.dozeTransitionModel.collect {
+ logger.log(TAG, VERBOSE, "Doze transition", it)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
index 0645236..9f563fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
@@ -23,3 +23,15 @@
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class KeyguardClockLog
+
+/** A [com.android.systemui.plugins.log.LogBuffer] for small keyguard clock logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardSmallClockLog
+
+/** A [com.android.systemui.plugins.log.LogBuffer] for large keyguard clock logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardLargeClockLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index bc29858..d7817e1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -335,13 +335,33 @@
}
/**
- * Provides a {@link LogBuffer} for keyguard clock logs.
+ * Provides a {@link LogBuffer} for general keyguard clock logs.
*/
@Provides
@SysUISingleton
@KeyguardClockLog
public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) {
- return factory.create("KeyguardClockLog", 500);
+ return factory.create("KeyguardClockLog", 100);
+ }
+
+ /**
+ * Provides a {@link LogBuffer} for keyguard small clock logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardSmallClockLog
+ public static LogBuffer provideKeyguardSmallClockLog(LogBufferFactory factory) {
+ return factory.create("KeyguardSmallClockLog", 100);
+ }
+
+ /**
+ * Provides a {@link LogBuffer} for keyguard large clock logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardLargeClockLog
+ public static LogBuffer provideKeyguardLargeClockLog(LogBufferFactory factory) {
+ return factory.create("KeyguardLargeClockLog", 100);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 9f44d98..935f38d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -150,7 +150,12 @@
logger: MediaTttLogger<ChipbarInfo>,
): ChipbarInfo {
val packageName = routeInfo.clientPackageName
- val otherDeviceName = routeInfo.name.toString()
+ val otherDeviceName =
+ if (routeInfo.name.isBlank()) {
+ context.getString(R.string.media_ttt_default_device_type)
+ } else {
+ routeInfo.name.toString()
+ }
return ChipbarInfo(
// Display the app's icon as the start icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 79fcc7d..1712490 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -24,6 +24,7 @@
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toolbar;
@@ -74,8 +75,8 @@
toolbar.setNavigationIcon(
getResources().getDrawable(value.resourceId, mContext.getTheme()));
- toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
- mContext.getString(com.android.internal.R.string.reset));
+ toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
toolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 2176825..35e9f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -21,7 +21,6 @@
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
-import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
@@ -122,7 +121,6 @@
mContext = context;
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
- mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart);
mProcessor = processor;
}
@@ -224,14 +222,8 @@
return;
}
- if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
- Log.d(TAG, "handleMessage: Using request processor");
- mProcessor.processAsync(request,
- (r) -> dispatchToController(r, onSaved, callback));
- return;
- }
-
- dispatchToController(request, onSaved, callback);
+ mProcessor.processAsync(request,
+ (r) -> dispatchToController(r, onSaved, callback));
}
private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 7fc0a5f..e406be1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -175,9 +175,10 @@
*/
var shadeExpandedFraction = -1f
set(value) {
- if (visible && field != value) {
+ if (field != value) {
header.alpha = ShadeInterpolation.getContentAlpha(value)
field = value
+ updateVisibility()
}
}
@@ -331,6 +332,9 @@
.setDuration(duration)
.alpha(if (show) 0f else 1f)
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
+ .setUpdateListener {
+ updateVisibility()
+ }
.start()
}
@@ -414,7 +418,7 @@
private fun updateVisibility() {
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
View.GONE
- } else if (qsVisible) {
+ } else if (qsVisible && header.alpha > 0f) {
View.VISIBLE
} else {
View.INVISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6a658b6..37d26c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -41,6 +41,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
+import static com.android.systemui.plugins.log.LogLevel.ERROR;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -1028,7 +1029,7 @@
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
} catch (RemoteException e) {
- mKeyguardLogger.logException(e, "Error calling IBatteryStats");
+ mKeyguardLogger.log(TAG, ERROR, "Error calling IBatteryStats", e);
mChargingTimeRemaining = -1;
}
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 56b689e..7d0ac18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -290,7 +290,8 @@
false,
null,
0,
- false
+ false,
+ 0
);
}
return ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 3483574..4ad3199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,6 +45,7 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
@@ -76,6 +77,7 @@
/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+ private static final String TAG = "KeyguardStatusBarViewController";
private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
@@ -422,7 +424,7 @@
/** Animate the keyguard status bar in. */
public void animateKeyguardStatusBarIn() {
- mLogger.d("animating status bar in");
+ mLogger.log(TAG, LogLevel.DEBUG, "animating status bar in");
if (mDisableStateTracker.isDisabled()) {
// If our view is disabled, don't allow us to animate in.
return;
@@ -438,7 +440,7 @@
/** Animate the keyguard status bar out. */
public void animateKeyguardStatusBarOut(long startDelay, long duration) {
- mLogger.d("animating status bar out");
+ mLogger.log(TAG, LogLevel.DEBUG, "animating status bar out");
ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
anim.addUpdateListener(mAnimatorUpdateListener);
anim.setStartDelay(startDelay);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index db1853d..52eef42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1487,6 +1487,7 @@
.setDuration(mDialogHideAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.withEndAction(() -> mHandler.postDelayed(() -> {
+ mController.notifyVisible(false);
mDialog.dismiss();
tryToRemoveCaptionsTooltip();
mIsAnimatingDismiss = false;
@@ -1497,7 +1498,6 @@
animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
mDialogHideAnimationDurationMs)).start();
checkODICaptionsTooltip(true);
- mController.notifyVisible(false);
synchronized (mSafetyWarningLock) {
if (mSafetyWarning != null) {
if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index b4baa44..c76b127 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -84,7 +84,8 @@
@Mock private lateinit var transitionRepository: KeyguardTransitionRepository
@Mock private lateinit var commandQueue: CommandQueue
private lateinit var repository: FakeKeyguardRepository
- @Mock private lateinit var logBuffer: LogBuffer
+ @Mock private lateinit var smallLogBuffer: LogBuffer
+ @Mock private lateinit var largeLogBuffer: LogBuffer
private lateinit var underTest: ClockEventController
@Before
@@ -111,7 +112,8 @@
context,
mainExecutor,
bgExecutor,
- logBuffer,
+ smallLogBuffer,
+ largeLogBuffer,
featureFlags
)
underTest.clock = clock
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index c8e7538..9a9acf3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.clocks.ClockRegistry;
@@ -115,6 +116,8 @@
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private LogBuffer mLogBuffer;
private final View mFakeSmartspaceView = new View(mContext);
@@ -156,7 +159,8 @@
mSecureSettings,
mExecutor,
mDumpManager,
- mClockEventController
+ mClockEventController,
+ mLogBuffer
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 254f953..8dc1e8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -189,6 +190,7 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -198,6 +200,7 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -212,6 +215,7 @@
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -223,6 +227,7 @@
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4cc12c7..f5b3959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -206,6 +206,21 @@
}
@Test
+ fun commandQueueCallback_almostCloseToStartCast_deviceNameBlank_showsDefaultDeviceName() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfoWithBlankDeviceName,
+ null,
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .contains(context.getString(R.string.media_ttt_default_device_type))
+ assertThat(chipbarView.getChipText())
+ .isNotEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+ }
+
+ @Test
fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
@@ -248,6 +263,21 @@
}
@Test
+ fun commandQueueCallback_transferToReceiverTriggered_deviceNameBlank_showsDefaultDeviceName() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfoWithBlankDeviceName,
+ null,
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .contains(context.getString(R.string.media_ttt_default_device_type))
+ assertThat(chipbarView.getChipText())
+ .isNotEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ }
+
+ @Test
fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
@@ -934,6 +964,7 @@
private const val APP_NAME = "Fake app name"
private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val BLANK_DEVICE_NAME = " "
private const val PACKAGE_NAME = "com.android.systemui"
private const val TIMEOUT = 10000
@@ -942,3 +973,9 @@
.addFeature("feature")
.setClientPackageName(PACKAGE_NAME)
.build()
+
+private val routeInfoWithBlankDeviceName =
+ MediaRoute2Info.Builder("id", BLANK_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index fa1fedb..99c79b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -39,7 +39,6 @@
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
@@ -101,7 +100,6 @@
}.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
// Flipped in selected test cases
- flags.set(SCREENSHOT_REQUEST_PROCESSOR, false)
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
service.attach(
@@ -149,31 +147,6 @@
}
@Test
- fun takeScreenshot_requestProcessorEnabled() {
- flags.set(SCREENSHOT_REQUEST_PROCESSOR, true)
-
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
-
- service.handleRequest(request, { /* onSaved */ }, callback)
-
- verify(controller, times(1)).takeScreenshotFullscreen(
- eq(topComponent),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
-
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
- val logEvent = eventLogger.get(0)
-
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
- }
-
- @Test
fun takeScreenshotProvidedImage() {
val bounds = Rect(50, 50, 150, 150)
val bitmap = makeHardwareBitmap(100, 100)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 1d30ad9..f580f5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -182,6 +182,7 @@
null
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+ whenever(view.alpha).thenReturn(1f)
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index b4c8f98..b568122 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.shade
+import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
import android.testing.AndroidTestingRunner
@@ -30,6 +31,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -37,6 +39,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
@@ -75,6 +78,7 @@
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
+ var viewAlpha = 1f
private lateinit var mLargeScreenShadeHeaderController: LargeScreenShadeHeaderController
private lateinit var carrierIconSlots: List<String>
@@ -101,6 +105,13 @@
null
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+
+ whenever(view.setAlpha(anyFloat())).then {
+ viewAlpha = it.arguments[0] as Float
+ null
+ }
+ whenever(view.alpha).thenAnswer { _ -> viewAlpha }
+
whenever(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
@@ -155,6 +166,16 @@
}
@Test
+ fun alphaChangesUpdateVisibility() {
+ makeShadeVisible()
+ mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+
+ mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
@@ -239,6 +260,39 @@
}
@Test
+ fun testShadeExpanded_true_alpha_zero_invisible() {
+ view.alpha = 0f
+ mLargeScreenShadeHeaderController.largeScreenActive = true
+ mLargeScreenShadeHeaderController.qsVisible = true
+
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun animatorCallsUpdateVisibilityOnUpdate() {
+ val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ whenever(view.animate()).thenReturn(animator)
+
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, 0L)
+
+ val updateCaptor = argumentCaptor<ValueAnimator.AnimatorUpdateListener>()
+ verify(animator).setUpdateListener(capture(updateCaptor))
+
+ mLargeScreenShadeHeaderController.largeScreenActive = true
+ mLargeScreenShadeHeaderController.qsVisible = true
+
+ view.alpha = 1f
+ updateCaptor.value.onAnimationUpdate(mock())
+
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+ view.alpha = 0f
+ updateCaptor.value.onAnimationUpdate(mock())
+
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
fun demoMode_attachDemoMode() {
val cb = argumentCaptor<DemoMode>()
verify(demoModeController).addCallback(capture(cb))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
index 8275c0c..9b3626b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -127,6 +127,6 @@
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
null, null, null, true, 0, false, -1, false, null, null, false, false,
- false, null, 0, false)
+ false, null, 0, false, 0)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c3c6975..d419095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume;
+import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertEquals;
@@ -342,6 +343,15 @@
assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
}
+ @Test
+ public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() {
+ mDialog.dismissH(DISMISS_REASON_UNKNOWN);
+ // notifyVisible(false) should not be called immediately but only after the dismiss
+ // animation has ended.
+ verify(mVolumeDialogController, times(0)).notifyVisible(false);
+ mDialog.getDialogView().animate().cancel();
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
index 045e6f1..7bcad45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -57,6 +59,7 @@
private ShortcutInfo mShortcutInfo = null;
private int mRankingAdjustment = 0;
private boolean mIsBubble = false;
+ private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
public RankingBuilder() {
}
@@ -86,6 +89,7 @@
mShortcutInfo = ranking.getConversationShortcutInfo();
mRankingAdjustment = ranking.getRankingAdjustment();
mIsBubble = ranking.isBubble();
+ mProposedImportance = ranking.getProposedImportance();
}
public Ranking build() {
@@ -114,7 +118,8 @@
mIsConversation,
mShortcutInfo,
mRankingAdjustment,
- mIsBubble);
+ mIsBubble,
+ mProposedImportance);
return ranking;
}
@@ -214,6 +219,11 @@
return this;
}
+ public RankingBuilder setProposedImportance(@Importance int importance) {
+ mProposedImportance = importance;
+ return this;
+ }
+
public RankingBuilder setUserSentiment(int userSentiment) {
mUserSentiment = userSentiment;
return this;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 736914a..278c98f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -82,6 +82,7 @@
private final @NonNull AudioService mAudioService;
private final @NonNull Context mContext;
+ private final @NonNull AudioSystemAdapter mAudioSystem;
/** ID for Communication strategy retrieved form audio policy manager */
private int mCommunicationStrategyId = -1;
@@ -156,12 +157,14 @@
public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
//-------------------------------------------------------------------
- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+ /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
+ @NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
mBtHelper = new BtHelper(this);
mDeviceInventory = new AudioDeviceInventory(this);
mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
+ mAudioSystem = audioSystem;
init();
}
@@ -170,12 +173,14 @@
* in system_server */
AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
@NonNull AudioDeviceInventory mockDeviceInventory,
- @NonNull SystemServerAdapter mockSystemServer) {
+ @NonNull SystemServerAdapter mockSystemServer,
+ @NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
mBtHelper = new BtHelper(this);
mDeviceInventory = mockDeviceInventory;
mSystemServer = mockSystemServer;
+ mAudioSystem = audioSystem;
init();
}
@@ -450,7 +455,7 @@
AudioAttributes attr =
AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(
AudioSystem.STREAM_VOICE_CALL);
- List<AudioDeviceAttributes> devices = AudioSystem.getDevicesForAttributes(
+ List<AudioDeviceAttributes> devices = mAudioSystem.getDevicesForAttributes(
attr, false /* forVolume */);
if (devices.isEmpty()) {
if (mAudioService.isPlatformVoice()) {
@@ -1225,7 +1230,7 @@
Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<"
+ fromA2dp + ">, eventSource<" + eventSource + ">)");
}
- AudioSystem.setForceUse(useCase, config);
+ mAudioSystem.setForceUse(useCase, config);
}
private void onSendBecomingNoisyIntent() {
@@ -1863,9 +1868,9 @@
if (preferredCommunicationDevice == null
|| preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
- AudioSystem.setParameters("BT_SCO=off");
+ mAudioSystem.setParameters("BT_SCO=off");
} else {
- AudioSystem.setParameters("BT_SCO=on");
+ mAudioSystem.setParameters("BT_SCO=on");
}
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c804ef2..148797f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -183,6 +183,7 @@
import com.android.server.SystemService;
import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
+import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
@@ -1205,7 +1206,7 @@
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
- mDeviceBroker = new AudioDeviceBroker(mContext, this);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -1637,7 +1638,7 @@
synchronized (mSettingsLock) {
final int forDock = mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
+ AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE;
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
sendEnabledSurroundFormats(mContentResolver, true);
@@ -2258,9 +2259,10 @@
SENDMSG_QUEUE,
AudioSystem.FOR_DOCK,
mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE,
+ AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE,
new String("readDockAudioSettings"),
0);
+
}
@@ -3741,19 +3743,30 @@
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
- AudioService.sVolumeLogger.loglogi("setDeviceVolume" + " from:" + callingPackage + " "
- + vi + " " + ada, TAG);
-
if (!vi.hasStreamType()) {
Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
return;
}
+
int index = vi.getVolumeIndex();
if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
throw new IllegalArgumentException(
"changing device volume requires a volume index or mute command");
}
+ // force a cache clear to force reevaluating stream type to audio device selection
+ // that can interfere with the sending of the VOLUME_CHANGED_ACTION intent
+ // TODO change cache management to not rely only on invalidation, but on "do not trust"
+ // moments when routing is in flux.
+ mAudioSystem.clearRoutingCache();
+
+ // log the current device that will be used when evaluating the sending of the
+ // VOLUME_CHANGED_ACTION intent to see if the current device is the one being modified
+ final int currDev = getDeviceForStream(vi.getStreamType());
+
+ AudioService.sVolumeLogger.log(new DeviceVolumeEvent(vi.getStreamType(), index, ada,
+ currDev, callingPackage));
+
// TODO handle unmuting of current audio device
// if a stream is not muted but the VolumeInfo is for muting, set the volume index
// for the device to min volume
@@ -3837,11 +3850,11 @@
return;
}
- final AudioEventLogger.Event event = (device == null)
- ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
- index/*val1*/, flags/*val2*/, callingPackage)
- : new DeviceVolumeEvent(streamType, index, device, callingPackage);
- sVolumeLogger.log(event);
+ if (device == null) {
+ // call was already logged in setDeviceVolume()
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ index/*val1*/, flags/*val2*/, callingPackage));
+ }
setStreamVolume(streamType, index, flags, device,
callingPackage, callingPackage, attributionTag,
Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
@@ -4242,7 +4255,11 @@
maybeSendSystemAudioStatusCommand(false);
}
}
- sendVolumeUpdate(streamType, oldIndex, index, flags, device);
+ if (ada == null) {
+ // only non-null when coming here from setDeviceVolume
+ // TODO change test to check early if device is current device or not
+ sendVolumeUpdate(streamType, oldIndex, index, flags, device);
+ }
}
@@ -7982,6 +7999,8 @@
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
+ AudioService.sVolumeLogger.log(new VolChangedBroadcastEvent(
+ mStreamType, mStreamVolumeAlias[mStreamType], index));
sendBroadcastToAll(mVolumeChanged);
}
}
@@ -10155,7 +10174,7 @@
static final int LOG_NB_EVENTS_PHONE_STATE = 20;
static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 50;
static final int LOG_NB_EVENTS_FORCE_USE = 20;
- static final int LOG_NB_EVENTS_VOLUME = 40;
+ static final int LOG_NB_EVENTS_VOLUME = 100;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
static final int LOG_NB_EVENTS_SPATIAL = 30;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 30a9e0a7..c2c3f02 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -147,19 +147,42 @@
}
}
+ static final class VolChangedBroadcastEvent extends AudioEventLogger.Event {
+ final int mStreamType;
+ final int mAliasStreamType;
+ final int mIndex;
+
+ VolChangedBroadcastEvent(int stream, int alias, int index) {
+ mStreamType = stream;
+ mAliasStreamType = alias;
+ mIndex = index;
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("sending VOLUME_CHANGED stream:")
+ .append(AudioSystem.streamToString(mStreamType))
+ .append(" index:").append(mIndex)
+ .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType))
+ .toString();
+ }
+ }
+
static final class DeviceVolumeEvent extends AudioEventLogger.Event {
final int mStream;
final int mVolIndex;
final String mDeviceNativeType;
final String mDeviceAddress;
final String mCaller;
+ final int mDeviceForStream;
DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
- String callingPackage) {
+ int deviceForStream, String callingPackage) {
mStream = streamType;
mVolIndex = index;
mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType());
mDeviceAddress = device.getAddress();
+ mDeviceForStream = deviceForStream;
mCaller = callingPackage;
// log metrics
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
@@ -180,7 +203,9 @@
.append(" index:").append(mVolIndex)
.append(" device:").append(mDeviceNativeType)
.append(" addr:").append(mDeviceAddress)
- .append(") from ").append(mCaller).toString();
+ .append(") from ").append(mCaller)
+ .append(" currDevForStream:Ox").append(Integer.toHexString(mDeviceForStream))
+ .toString();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index c3754eb..2588371 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -105,6 +105,13 @@
}
}
+ public void clearRoutingCache() {
+ if (DEBUG_CACHE) {
+ Log.d(TAG, "---- routing cache clear (from java) ----------");
+ }
+ invalidateRoutingCache();
+ }
+
/**
* Implementation of AudioSystem.VolumeRangeInitRequestCallback
*/
@@ -337,6 +344,7 @@
* @return
*/
public int setParameters(String keyValuePairs) {
+ invalidateRoutingCache();
return AudioSystem.setParameters(keyValuePairs);
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 54be4bb..1862942 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -58,13 +58,15 @@
public final class PlaybackActivityMonitor
implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
- public static final String TAG = "AudioService.PlaybackActivityMonitor";
+ public static final String TAG = "AS.PlayActivityMonitor";
/*package*/ static final boolean DEBUG = false;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
+ /*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
+ // ducking settings for a "normal duck" at -14dB
private static final VolumeShaper.Configuration DUCK_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
@@ -78,6 +80,22 @@
.build();
private static final VolumeShaper.Configuration DUCK_ID =
new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID);
+
+ // ducking settings for a "strong duck" at -35dB (attenuation factor of 0.017783)
+ private static final VolumeShaper.Configuration STRONG_DUCK_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID)
+ .setCurve(new float[] { 0.f, 1.f } /* times */,
+ new float[] { 1.f, 0.017783f } /* volumes */)
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(MediaFocusControl.getFocusRampTimeMs(
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build()))
+ .build();
+ private static final VolumeShaper.Configuration STRONG_DUCK_ID =
+ new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID);
+
private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
.createIfNeeded()
@@ -659,11 +677,23 @@
// add the players eligible for ducking to the list, and duck them
// (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
// players of the same uid start, they will be ducked by DuckingManager.checkDuck())
- mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);
+ mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck, reqCausesStrongDuck(winner));
}
return true;
}
+ private boolean reqCausesStrongDuck(FocusRequester requester) {
+ if (requester.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
+ return false;
+ }
+ final int reqUsage = requester.getAudioAttributes().getUsage();
+ if ((reqUsage == AudioAttributes.USAGE_ASSISTANT)
+ || (reqUsage == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)) {
+ return true;
+ }
+ return false;
+ }
+
@Override
public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
@@ -939,10 +969,11 @@
private static final class DuckingManager {
private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();
- synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) {
+ synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck,
+ boolean requestCausesStrongDuck) {
if (DEBUG) { Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
if (!mDuckers.containsKey(uid)) {
- mDuckers.put(uid, new DuckedApp(uid));
+ mDuckers.put(uid, new DuckedApp(uid, requestCausesStrongDuck));
}
final DuckedApp da = mDuckers.get(uid);
for (AudioPlaybackConfiguration apc : apcsToDuck) {
@@ -989,10 +1020,13 @@
private static final class DuckedApp {
private final int mUid;
+ /** determines whether ducking is done with DUCK_VSHAPE or STRONG_DUCK_VSHAPE */
+ private final boolean mUseStrongDuck;
private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
- DuckedApp(int uid) {
+ DuckedApp(int uid, boolean useStrongDuck) {
mUid = uid;
+ mUseStrongDuck = useStrongDuck;
}
void dump(PrintWriter pw) {
@@ -1013,9 +1047,9 @@
return;
}
try {
- sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
+ sEventLogger.log((new DuckEvent(apc, skipRamp, mUseStrongDuck)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
- DUCK_VSHAPE,
+ mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
mDuckedPlayers.add(piid);
} catch (Exception e) {
@@ -1031,7 +1065,7 @@
sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+ piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
- DUCK_ID,
+ mUseStrongDuck ? STRONG_DUCK_ID : DUCK_ID,
VolumeShaper.Operation.REVERSE);
} catch (Exception e) {
Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e);
@@ -1146,13 +1180,17 @@
}
static final class DuckEvent extends VolumeShaperEvent {
+ final boolean mUseStrongDuck;
+
@Override
String getVSAction() {
- return "ducking";
+ return mUseStrongDuck ? "ducking (strong)" : "ducking";
}
- DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+ DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
+ {
super(apc, skipRamp);
+ mUseStrongDuck = useStrongDuck;
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4d44c886..6bc8582 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -239,7 +239,6 @@
import android.service.notification.NotificationRecordProto;
import android.service.notification.NotificationServiceDumpProto;
import android.service.notification.NotificationStats;
-import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeProto;
@@ -8526,95 +8525,6 @@
}
}
- static class NotificationRecordExtractorData {
- // Class that stores any field in a NotificationRecord that can change via an extractor.
- // Used to cache previous data used in a sort.
- int mPosition;
- int mVisibility;
- boolean mShowBadge;
- boolean mAllowBubble;
- boolean mIsBubble;
- NotificationChannel mChannel;
- String mGroupKey;
- ArrayList<String> mOverridePeople;
- ArrayList<SnoozeCriterion> mSnoozeCriteria;
- Integer mUserSentiment;
- Integer mSuppressVisually;
- ArrayList<Notification.Action> mSystemSmartActions;
- ArrayList<CharSequence> mSmartReplies;
- int mImportance;
-
- // These fields may not trigger a reranking but diffs here may be logged.
- float mRankingScore;
- boolean mIsConversation;
-
- NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
- boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
- ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
- Integer userSentiment, Integer suppressVisually,
- ArrayList<Notification.Action> systemSmartActions,
- ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
- boolean isConversation) {
- mPosition = position;
- mVisibility = visibility;
- mShowBadge = showBadge;
- mAllowBubble = allowBubble;
- mIsBubble = isBubble;
- mChannel = channel;
- mGroupKey = groupKey;
- mOverridePeople = overridePeople;
- mSnoozeCriteria = snoozeCriteria;
- mUserSentiment = userSentiment;
- mSuppressVisually = suppressVisually;
- mSystemSmartActions = systemSmartActions;
- mSmartReplies = smartReplies;
- mImportance = importance;
- mRankingScore = rankingScore;
- mIsConversation = isConversation;
- }
-
- // Returns whether the provided NotificationRecord differs from the cached data in any way.
- // Should be guarded by mNotificationLock; not annotated here as this class is static.
- boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
- return mPosition != newPosition
- || mVisibility != r.getPackageVisibilityOverride()
- || mShowBadge != r.canShowBadge()
- || mAllowBubble != r.canBubble()
- || mIsBubble != r.getNotification().isBubbleNotification()
- || !Objects.equals(mChannel, r.getChannel())
- || !Objects.equals(mGroupKey, r.getGroupKey())
- || !Objects.equals(mOverridePeople, r.getPeopleOverride())
- || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
- || !Objects.equals(mUserSentiment, r.getUserSentiment())
- || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
- || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
- || !Objects.equals(mSmartReplies, r.getSmartReplies())
- || mImportance != r.getImportance();
- }
-
- // Returns whether the NotificationRecord has a change from this data for which we should
- // log an update. This method specifically targets fields that may be changed via
- // adjustments from the assistant.
- //
- // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
- // and NotificationRecord.applyAdjustments.
- //
- // Should be guarded by mNotificationLock; not annotated here as this class is static.
- boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
- return mPosition != newPosition
- || !Objects.equals(mChannel, r.getChannel())
- || !Objects.equals(mGroupKey, r.getGroupKey())
- || !Objects.equals(mOverridePeople, r.getPeopleOverride())
- || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
- || !Objects.equals(mUserSentiment, r.getUserSentiment())
- || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
- || !Objects.equals(mSmartReplies, r.getSmartReplies())
- || mImportance != r.getImportance()
- || !r.rankingScoreMatches(mRankingScore)
- || mIsConversation != r.isConversation();
- }
- }
-
void handleRankingSort() {
if (mRankingHelper == null) return;
synchronized (mNotificationLock) {
@@ -8640,7 +8550,8 @@
r.getSmartReplies(),
r.getImportance(),
r.getRankingScore(),
- r.isConversation());
+ r.isConversation(),
+ r.getProposedImportance());
extractorDataBefore.put(r.getKey(), extractorData);
mRankingHelper.extractSignals(r);
}
@@ -9935,7 +9846,8 @@
record.getRankingScore() == 0
? RANKING_UNCHANGED
: (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED),
- record.getNotification().isBubbleNotification()
+ record.getNotification().isBubbleNotification(),
+ record.getProposedImportance()
);
rankings.add(ranking);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index cbaf485..d344306 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -210,6 +210,7 @@
// Whether this notification record should have an update logged the next time notifications
// are sorted.
private boolean mPendingLogUpdate = false;
+ private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
@@ -499,6 +500,8 @@
pw.println(prefix + "mImportance="
+ NotificationListenerService.Ranking.importanceToString(mImportance));
pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation());
+ pw.println(prefix + "mProposedImportance="
+ + NotificationListenerService.Ranking.importanceToString(mProposedImportance));
pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
pw.println(prefix + "mIntercept=" + mIntercept);
pw.println(prefix + "mHidden==" + mHidden);
@@ -738,6 +741,12 @@
Adjustment.KEY_NOT_CONVERSATION,
Boolean.toString(mIsNotConversationOverride));
}
+ if (signals.containsKey(Adjustment.KEY_IMPORTANCE_PROPOSAL)) {
+ mProposedImportance = signals.getInt(Adjustment.KEY_IMPORTANCE_PROPOSAL);
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_IMPORTANCE_PROPOSAL,
+ Integer.toString(mProposedImportance));
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
@@ -870,6 +879,10 @@
return stats.naturalImportance;
}
+ public int getProposedImportance() {
+ return mProposedImportance;
+ }
+
public float getRankingScore() {
return mRankingScore;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
new file mode 100644
index 0000000..6dc9029
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.service.notification.SnoozeCriterion;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Class that stores any field in a NotificationRecord that can change via an extractor.
+ * Used to cache previous data used in a sort.
+ */
+public final class NotificationRecordExtractorData {
+ private final int mPosition;
+ private final int mVisibility;
+ private final boolean mShowBadge;
+ private final boolean mAllowBubble;
+ private final boolean mIsBubble;
+ private final NotificationChannel mChannel;
+ private final String mGroupKey;
+ private final ArrayList<String> mOverridePeople;
+ private final ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private final Integer mUserSentiment;
+ private final Integer mSuppressVisually;
+ private final ArrayList<Notification.Action> mSystemSmartActions;
+ private final ArrayList<CharSequence> mSmartReplies;
+ private final int mImportance;
+
+ // These fields may not trigger a reranking but diffs here may be logged.
+ private final float mRankingScore;
+ private final boolean mIsConversation;
+ private final int mProposedImportance;
+
+ NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
+ boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
+ ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
+ Integer userSentiment, Integer suppressVisually,
+ ArrayList<Notification.Action> systemSmartActions,
+ ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
+ boolean isConversation, int proposedImportance) {
+ mPosition = position;
+ mVisibility = visibility;
+ mShowBadge = showBadge;
+ mAllowBubble = allowBubble;
+ mIsBubble = isBubble;
+ mChannel = channel;
+ mGroupKey = groupKey;
+ mOverridePeople = overridePeople;
+ mSnoozeCriteria = snoozeCriteria;
+ mUserSentiment = userSentiment;
+ mSuppressVisually = suppressVisually;
+ mSystemSmartActions = systemSmartActions;
+ mSmartReplies = smartReplies;
+ mImportance = importance;
+ mRankingScore = rankingScore;
+ mIsConversation = isConversation;
+ mProposedImportance = proposedImportance;
+ }
+
+ // Returns whether the provided NotificationRecord differs from the cached data in any way.
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || mVisibility != r.getPackageVisibilityOverride()
+ || mShowBadge != r.canShowBadge()
+ || mAllowBubble != r.canBubble()
+ || mIsBubble != r.getNotification().isBubbleNotification()
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || mProposedImportance != r.getProposedImportance();
+ }
+
+ // Returns whether the NotificationRecord has a change from this data for which we should
+ // log an update. This method specifically targets fields that may be changed via
+ // adjustments from the assistant.
+ //
+ // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
+ // and NotificationRecord.applyAdjustments.
+ //
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || !r.rankingScoreMatches(mRankingScore)
+ || mIsConversation != r.isConversation()
+ || mProposedImportance != r.getProposedImportance();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d8aa469..cdcf6c4 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1005,6 +1005,7 @@
channel.setAllowBubbles(existing != null
? existing.getAllowBubbles()
: NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setImportantConversation(false);
}
clearLockedFieldsLocked(channel);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 00fb065..866a995 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -91,6 +91,7 @@
import android.service.gatekeeper.IGateKeeperService;
import android.service.voice.VoiceInteractionManagerInternal;
import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1760,6 +1761,63 @@
}
}
+ /**
+ * Returns whether switching users is currently allowed for the provided user.
+ * <p>
+ * Switching users is not allowed in the following cases:
+ * <li>the user is in a phone call</li>
+ * <li>{@link UserManager#DISALLOW_USER_SWITCH} is set</li>
+ * <li>system user hasn't been unlocked yet</li>
+ *
+ * @return A {@link UserManager.UserSwitchabilityResult} flag indicating if the user is
+ * switchable.
+ */
+ public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability");
+
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("getUserSwitchability-" + userId);
+
+ int flags = UserManager.SWITCHABILITY_STATUS_OK;
+
+ t.traceBegin("TM.isInCall");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ if (telecomManager != null && telecomManager.isInCall()) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ t.traceEnd();
+
+ t.traceBegin("hasUserRestriction-DISALLOW_USER_SWITCH");
+ if (mLocalService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)) {
+ flags |= UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
+ }
+ t.traceEnd();
+
+ // System User is always unlocked in Headless System User Mode, so ignore this flag
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ t.traceBegin("getInt-ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED");
+ final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+ t.traceEnd();
+ t.traceBegin("isUserUnlocked-USER_SYSTEM");
+ final boolean systemUserUnlocked = mLocalService.isUserUnlocked(UserHandle.USER_SYSTEM);
+ t.traceEnd();
+
+ if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
+ flags |= UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+ }
+ }
+ t.traceEnd();
+
+ return flags;
+ }
+
@Override
public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index ba0413d..c6037da 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -203,8 +203,11 @@
|| !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
return;
}
- boolean cycleThroughStop = mWmService.mLetterboxConfiguration
- .isCameraCompatRefreshCycleThroughStopEnabled();
+ boolean cycleThroughStop =
+ mWmService.mLetterboxConfiguration
+ .isCameraCompatRefreshCycleThroughStopEnabled()
+ && !activity.mLetterboxUiController
+ .shouldRefreshActivityViaPauseForCameraCompat();
try {
activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
@@ -255,7 +258,8 @@
Configuration lastReportedConfig) {
return newConfig.windowConfiguration.getDisplayRotation()
!= lastReportedConfig.windowConfiguration.getDisplayRotation()
- && isTreatmentEnabledForActivity(activity);
+ && isTreatmentEnabledForActivity(activity)
+ && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
}
/**
@@ -294,7 +298,8 @@
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
&& activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
- && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+ && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
+ && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
private synchronized void notifyCameraOpened(
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0c8a645..75ba214 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,12 +17,18 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -132,6 +138,15 @@
@Nullable
private Letterbox mLetterbox;
+ @Nullable
+ private final Boolean mBooleanPropertyCameraCompatAllowForceRotation;
+
+ @Nullable
+ private final Boolean mBooleanPropertyCameraCompatAllowRefresh;
+
+ @Nullable
+ private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause;
+
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
// DisplayRotationCompatPolicy.
@@ -154,8 +169,33 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ mBooleanPropertyCameraCompatAllowForceRotation =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true),
+ PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
+ mBooleanPropertyCameraCompatAllowRefresh =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true),
+ PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
+ mBooleanPropertyCameraCompatEnableRefreshViaPause =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true),
+ PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
}
+ /**
+ * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code
+ * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the
+ * property isn't specified for the package.
+ *
+ * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the
+ * property is unset. Particularly, when this returns {@code null}, {@link
+ * #shouldEnableWithOverrideAndProperty} will check the value of override for the final
+ * decision.
+ */
@Nullable
private static Boolean readComponentProperty(PackageManager packageManager, String packageName,
BooleanSupplier gatingCondition, String propertyName) {
@@ -210,15 +250,11 @@
* </ul>
*/
boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) {
- if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) {
- return false;
- }
- if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) {
- return false;
- }
- if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation)
- && !mActivityRecord.info.isChangeEnabled(
- OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) {
+ if (!shouldEnableWithOverrideAndProperty(
+ /* gatingCondition */ mLetterboxConfiguration
+ ::isPolicyForIgnoringRequestedOrientationEnabled,
+ OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION,
+ mBooleanPropertyIgnoreRequestedOrientation)) {
return false;
}
if (mIsRelauchingAfterRequestedOrientationChanged) {
@@ -262,6 +298,109 @@
mIsRefreshAfterRotationRequested = isRequested;
}
+ /**
+ * Whether activity is eligible for activity "refresh" after camera compat force rotation
+ * treatment. See {@link DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override or by the app
+ * developers with the component property.
+ * </ul>
+ */
+ boolean shouldRefreshActivityForCameraCompat() {
+ return shouldEnableWithOptOutOverrideAndProperty(
+ /* gatingCondition */ () -> mLetterboxConfiguration
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+ OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH,
+ mBooleanPropertyCameraCompatAllowRefresh);
+ }
+
+ /**
+ * Whether activity should be "refreshed" after the camera compat force rotation treatment
+ * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped
+ * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the
+ * component property by the app developers.
+ * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device
+ * manufacturer with override / by the app developers with the component property.
+ * </ul>
+ */
+ boolean shouldRefreshActivityViaPauseForCameraCompat() {
+ return shouldEnableWithOverrideAndProperty(
+ /* gatingCondition */ () -> mLetterboxConfiguration
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+ OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
+ mBooleanPropertyCameraCompatEnableRefreshViaPause);
+ }
+
+ /**
+ * Whether activity is eligible for camera compat force rotation treatment. See {@link
+ * DisplayRotationCompatPolicy} for context.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the camera compat treatment is enabled.
+ * <li>Activity isn't opted out by the device manufacturer with override or by the app
+ * developers with the component property.
+ * </ul>
+ */
+ boolean shouldForceRotateForCameraCompat() {
+ return shouldEnableWithOptOutOverrideAndProperty(
+ /* gatingCondition */ () -> mLetterboxConfiguration
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+ OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION,
+ mBooleanPropertyCameraCompatAllowForceRotation);
+ }
+
+ /**
+ * Returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>{@code gatingCondition} isn't {@code false}
+ * <li>OEM didn't opt out with a {@code overrideChangeId} override
+ * <li>App developers didn't opt out with a component {@code property}
+ * </ul>
+ *
+ * <p>This is used for the treatments that are enabled based with the heuristic but can be
+ * disabled on per-app basis by OEMs or app developers.
+ */
+ private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition,
+ long overrideChangeId, Boolean property) {
+ if (!gatingCondition.getAsBoolean()) {
+ return false;
+ }
+ return !Boolean.FALSE.equals(property)
+ && !mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ }
+
+ /**
+ * Returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>{@code gatingCondition} isn't {@code false}
+ * <li>App developers didn't opt out with a component {@code property}
+ * <li>App developers opted in with a component {@code property} or an OEM opted in with a
+ * component {@code property}
+ * </ul>
+ *
+ * <p>This is used for the treatments that are enabled only on per-app basis.
+ */
+ private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition,
+ long overrideChangeId, Boolean property) {
+ if (!gatingCondition.getAsBoolean()) {
+ return false;
+ }
+ if (Boolean.FALSE.equals(property)) {
+ return false;
+ }
+ return Boolean.TRUE.equals(property)
+ || mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ }
+
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index dad9fe8..31599ee 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -74,7 +74,7 @@
mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
mSpySystemServer = spy(new NoOpSystemServerAdapter());
mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
- mSpySystemServer);
+ mSpySystemServer, mSpyAudioSystem);
mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 66c3f07..b12e6ad 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -1678,7 +1678,7 @@
mParentNotificationChannel.getImportance(),
null, null,
mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
- false, false, false, null, 0, false);
+ false, false, false, null, 0, false, 0);
return true;
}).when(mRankingMap).getRanking(eq(GENERIC_KEY),
any(NotificationListenerService.Ranking.class));
@@ -1704,7 +1704,7 @@
mNotificationChannel.getImportance(),
null, null,
mNotificationChannel, null, null, true, 0, false, -1, false, null, null, false,
- false, false, null, 0, false);
+ false, false, null, 0, false, 0);
return true;
}).when(mRankingMap).getRanking(eq(CUSTOM_KEY),
any(NotificationListenerService.Ranking.class));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 12cd834..8a99c2c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -193,7 +193,8 @@
tweak.isConversation(),
tweak.getConversationShortcutInfo(),
tweak.getRankingAdjustment(),
- tweak.isBubble()
+ tweak.isBubble(),
+ tweak.getProposedImportance()
);
assertNotEquals(nru, nru2);
}
@@ -274,7 +275,8 @@
isConversation(i),
getShortcutInfo(i),
getRankingAdjustment(i),
- isBubble(i)
+ isBubble(i),
+ getProposedImportance(i)
);
rankings[i] = ranking;
}
@@ -402,6 +404,10 @@
return index % 3 - 1;
}
+ private int getProposedImportance(int index) {
+ return index % 5 - 1;
+ }
+
private boolean isBubble(int index) {
return index % 4 == 0;
}
@@ -443,6 +449,7 @@
assertEquals(comment, a.getConversationShortcutInfo().getId(),
b.getConversationShortcutInfo().getId());
assertActionsEqual(a.getSmartActions(), b.getSmartActions());
+ assertEquals(a.getProposedImportance(), b.getProposedImportance());
}
private void detailedAssertEquals(RankingMap a, RankingMap b) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
new file mode 100644
index 0000000..87e86cb
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.service.notification.Adjustment;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
+
+ @Test
+ public void testHasDiffs_noDiffs() {
+ NotificationRecord r = generateRecord();
+
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ 1,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation(),
+ r.getProposedImportance());
+
+ assertFalse(extractorData.hasDiffForRankingLocked(r, 1));
+ assertFalse(extractorData.hasDiffForLoggingLocked(r, 1));
+ }
+
+ @Test
+ public void testHasDiffs_proposedImportanceChange() {
+ NotificationRecord r = generateRecord();
+
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ 1,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation(),
+ r.getProposedImportance());
+
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH);
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+ r.addAdjustment(adjustment);
+ r.applyAdjustments();
+
+ assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+ assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+ }
+
+ private NotificationRecord generateRecord() {
+ NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
+ final Notification.Builder builder = new Notification.Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0,
+ 0, n, UserHandle.ALL, null, System.currentTimeMillis());
+ return new NotificationRecord(getContext(), sbn, channel);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 5468220..14b0048 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -19,6 +19,7 @@
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -755,6 +756,24 @@
}
@Test
+ public void testProposedImportance() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(IMPORTANCE_UNSPECIFIED, record.getProposedImportance());
+
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_DEFAULT);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getProposedImportance());
+ }
+
+ @Test
public void testAppImportance_returnsCorrectly() {
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 598a22b..770feab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1703,6 +1703,7 @@
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.setShowBadge(true);
channel.setAllowBubbles(false);
+ channel.setImportantConversation(true);
int lockMask = 0;
for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
@@ -1718,6 +1719,7 @@
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
assertFalse(savedChannel.canBypassDnd());
assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertFalse(channel.isImportantConversation());
assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
assertEquals(channel.canBubble(), savedChannel.canBubble());
@@ -4396,7 +4398,7 @@
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
channel2.setConversationId(calls.getId(), convoId);
channel2.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, false, false);
List<ConversationChannelWrapper> convos =
mHelper.getConversations(IntArray.wrap(new int[] {0}), false);
@@ -4473,7 +4475,7 @@
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
channel2.setConversationId(calls.getId(), convoId);
channel2.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel2, false, false);
List<ConversationChannelWrapper> convos =
mHelper.getConversations(IntArray.wrap(new int[] {0}), false);
@@ -4501,13 +4503,13 @@
new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
channel.setConversationId(messages.getId(), convoId);
channel.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false);
NotificationChannel diffConvo =
new NotificationChannel("B person msgs", "messages from B", IMPORTANCE_DEFAULT);
diffConvo.setConversationId(p.getId(), "different convo");
diffConvo.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, true, false);
+ mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, false, false);
NotificationChannel channel2 =
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
@@ -4534,7 +4536,7 @@
new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
channel.setConversationId(messages.getId(), convoId);
channel.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false);
mHelper.permanentlyDeleteNotificationChannel(PKG_O, UID_O, "messages");
@@ -4935,7 +4937,7 @@
"conversation", IMPORTANCE_DEFAULT);
friend.setConversationId(parent.getId(), "friend");
friend.setImportantConversation(true);
- mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, false, false);
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index e30e5db..74ea7d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -40,7 +40,7 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
@@ -242,11 +242,11 @@
private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 8bb79e3..45b30b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -155,6 +155,18 @@
}
@Test
+ public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.mLetterboxUiController.shouldForceRotateForCameraCompat())
+ .thenReturn(false);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
@@ -327,7 +339,21 @@
}
@Test
- public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
+ public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+ .thenReturn(false);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh()
+ throws Exception {
when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -362,6 +388,19 @@
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat())
+ .thenReturn(true);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 6d778afe..5e087f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -16,8 +16,14 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -74,6 +80,8 @@
mController = new LetterboxUiController(mWm, mActivity);
}
+ // shouldIgnoreRequestedOrientation
+
@Test
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() {
@@ -134,7 +142,7 @@
}
@Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() {
prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
doReturn(false).when(mLetterboxConfiguration)
@@ -143,6 +151,163 @@
assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
}
+ // shouldRefreshActivityForCameraCompat
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
+ doReturn(false).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ @Test
+ public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldRefreshActivityForCameraCompat());
+ }
+
+ // shouldRefreshActivityViaPauseForCameraCompat
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
+ doReturn(false).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
+ public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ @Test
+ public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
+ }
+
+ // shouldForceRotateForCameraCompat
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
+ doReturn(false).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+ public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
+ public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldForceRotateForCameraCompat());
+ }
+
+ @Test
+ public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+ mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldForceRotateForCameraCompat());
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 55bf2ab..1c57ba5 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -55,6 +55,7 @@
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1152,6 +1153,11 @@
Slog.w(TAG, "Failed to report onError status: " + e);
}
}
+ // Can improve to log exit reason if needed
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
+ mVoiceInteractionServiceUid);
}
@Override