Merge "Make "ime reset --user <userId>" consistent among users" into main
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cbabb02..90de7ab 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2214,6 +2214,9 @@
notifyVoiceInteractionManagerServiceActivityEvent(
VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+ // Notify autofill
+ getAutofillClientController().onActivityPostResumed();
+
mCalled = true;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4952af3..8696e0f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4623,7 +4623,7 @@
private void scheduleResume(ActivityClientRecord r) {
final ClientTransaction transaction = new ClientTransaction(mAppThread);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(r.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
transaction.addTransactionItem(resumeActivityItem);
executeTransaction(transaction);
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index 48db18f..d07ad46 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.os.IBinder;
import android.os.Parcel;
import java.lang.annotation.Retention;
@@ -25,6 +26,7 @@
/**
* Request for lifecycle state that an activity should reach.
+ *
* @hide
*/
public abstract class ActivityLifecycleItem extends ActivityTransactionItem {
@@ -52,8 +54,19 @@
public static final int ON_DESTROY = 6;
public static final int ON_RESTART = 7;
- ActivityLifecycleItem() {}
+ ActivityLifecycleItem(@NonNull IBinder activityToken) {
+ super(activityToken);
+ }
+ // TODO(b/311089192): Remove this method once no subclasses obtain from the object pool.
+ @Deprecated
+ ActivityLifecycleItem() {
+ super();
+ }
+
+ // Parcelable implementation
+
+ /** Reads from Parcel. */
ActivityLifecycleItem(@NonNull Parcel in) {
super(in);
}
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index b4ff476f..8ef86ee 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -49,9 +49,29 @@
public abstract class ActivityTransactionItem extends ClientTransactionItem {
/** Target client activity. */
+ // TODO(b/311089192): Mark this with @NonNull and final.
private IBinder mActivityToken;
- ActivityTransactionItem() {}
+ public ActivityTransactionItem(@NonNull IBinder activityToken) {
+ mActivityToken = requireNonNull(activityToken);
+ }
+
+ // TODO(b/311089192): Remove this method once no subclasses obtain from the object pool.
+ @Deprecated
+ ActivityTransactionItem() {
+ }
+
+ /**
+ * Sets the activity token after the instance is obtained from the object pool.
+ *
+ * @deprecated This method is deprecated. The object pool is no longer used.
+ * Instead, directly create a new object with the activity token.
+ * TODO(b/311089192): Remove this method once no subclasses obtain from the object pool.
+ */
+ @Deprecated
+ void setActivityToken(@NonNull IBinder activityToken) {
+ mActivityToken = requireNonNull(activityToken);
+ }
@Override
public final void execute(@NonNull ClientTransactionHandler client,
@@ -94,26 +114,18 @@
return mActivityToken;
}
- void setActivityToken(@NonNull IBinder activityToken) {
- mActivityToken = requireNonNull(activityToken);
- }
+ // Parcelable implementation
- // To be overridden
-
- ActivityTransactionItem(@NonNull Parcel in) {
- mActivityToken = in.readStrongBinder();
- }
-
+ /** Writes to Parcel. */
@CallSuper
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mActivityToken);
}
- @CallSuper
- @Override
- public void recycle() {
- mActivityToken = null;
+ /** Reads from Parcel. */
+ ActivityTransactionItem(@NonNull Parcel in) {
+ this(in.readStrongBinder());
}
@Override
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index b0213d7..8a81143 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -28,11 +28,17 @@
/**
* Request to destroy an activity.
+ *
* @hide
*/
public class DestroyActivityItem extends ActivityLifecycleItem {
- private boolean mFinished;
+ private final boolean mFinished;
+
+ public DestroyActivityItem(@NonNull IBinder activityToken, boolean finished) {
+ super(activityToken);
+ mFinished = finished;
+ }
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -60,40 +66,16 @@
return ON_DESTROY;
}
- // ObjectPoolItem implementation
-
- private DestroyActivityItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) {
- DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
- if (instance == null) {
- instance = new DestroyActivityItem();
- }
- instance.setActivityToken(activityToken);
- instance.mFinished = finished;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mFinished = false;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private DestroyActivityItem(@NonNull Parcel in) {
super(in);
mFinished = in.readBoolean();
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index 4a0ea98..a28791f 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.app.ActivityClient;
import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
@@ -30,22 +31,37 @@
/**
* Request to move an activity to resumed state.
+ *
* @hide
*/
public class ResumeActivityItem extends ActivityLifecycleItem {
- private static final String TAG = "ResumeActivityItem";
+ @ProcessState
+ private final int mProcState;
- private int mProcState;
- private boolean mUpdateProcState;
- private boolean mIsForward;
+ private final boolean mIsForward;
+
// Whether we should send compat fake focus when the activity is resumed. This is needed
// because some game engines wait to get focus before drawing the content of the app.
- private boolean mShouldSendCompatFakeFocus;
+ private final boolean mShouldSendCompatFakeFocus;
+
+ public ResumeActivityItem(@NonNull IBinder activityToken, boolean isForward,
+ boolean shouldSendCompatFakeFocus) {
+ this(activityToken, ActivityManager.PROCESS_STATE_UNKNOWN, isForward,
+ shouldSendCompatFakeFocus);
+ }
+
+ public ResumeActivityItem(@NonNull IBinder activityToken, @ProcessState int procState,
+ boolean isForward, boolean shouldSendCompatFakeFocus) {
+ super(activityToken);
+ mProcState = procState;
+ mIsForward = isForward;
+ mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus;
+ }
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
- if (mUpdateProcState) {
+ if (mProcState != ActivityManager.PROCESS_STATE_UNKNOWN) {
client.updateProcessState(mProcState, false);
}
}
@@ -72,71 +88,21 @@
return ON_RESUME;
}
- // ObjectPoolItem implementation
-
- private ResumeActivityItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static ResumeActivityItem obtain(@NonNull IBinder activityToken, int procState,
- boolean isForward, boolean shouldSendCompatFakeFocus) {
- ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
- if (instance == null) {
- instance = new ResumeActivityItem();
- }
- instance.setActivityToken(activityToken);
- instance.mProcState = procState;
- instance.mUpdateProcState = true;
- instance.mIsForward = isForward;
- instance.mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus;
-
- return instance;
- }
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static ResumeActivityItem obtain(@NonNull IBinder activityToken, boolean isForward,
- boolean shouldSendCompatFakeFocus) {
- ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
- if (instance == null) {
- instance = new ResumeActivityItem();
- }
- instance.setActivityToken(activityToken);
- instance.mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
- instance.mUpdateProcState = false;
- instance.mIsForward = isForward;
- instance.mShouldSendCompatFakeFocus = shouldSendCompatFakeFocus;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
- mUpdateProcState = false;
- mIsForward = false;
- mShouldSendCompatFakeFocus = false;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mProcState);
- dest.writeBoolean(mUpdateProcState);
dest.writeBoolean(mIsForward);
dest.writeBoolean(mShouldSendCompatFakeFocus);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private ResumeActivityItem(@NonNull Parcel in) {
super(in);
mProcState = in.readInt();
- mUpdateProcState = in.readBoolean();
mIsForward = in.readBoolean();
mShouldSendCompatFakeFocus = in.readBoolean();
}
@@ -160,7 +126,7 @@
return false;
}
final ResumeActivityItem other = (ResumeActivityItem) o;
- return mProcState == other.mProcState && mUpdateProcState == other.mUpdateProcState
+ return mProcState == other.mProcState
&& mIsForward == other.mIsForward
&& mShouldSendCompatFakeFocus == other.mShouldSendCompatFakeFocus;
}
@@ -170,7 +136,6 @@
int result = 17;
result = 31 * result + super.hashCode();
result = 31 * result + mProcState;
- result = 31 * result + (mUpdateProcState ? 1 : 0);
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + (mShouldSendCompatFakeFocus ? 1 : 0);
return result;
@@ -180,7 +145,6 @@
public String toString() {
return "ResumeActivityItem{" + super.toString()
+ ",procState=" + mProcState
- + ",updateProcState=" + mUpdateProcState
+ ",isForward=" + mIsForward
+ ",shouldSendCompatFakeFocus=" + mShouldSendCompatFakeFocus + "}";
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 9f622e9..8ba9076 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -42,6 +42,7 @@
/**
* Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution.
+ *
* @hide
*/
public class TransactionExecutorHelper {
@@ -203,7 +204,7 @@
lifecycleItem = StopActivityItem.obtain(r.token);
break;
default:
- lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
+ lifecycleItem = new ResumeActivityItem(r.token, false /* isForward */,
false /* shouldSendCompatFakeFocus */);
break;
}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5a39702..ce241c1 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -337,3 +337,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "private_space_search_illustration_config"
+ namespace: "profile_experiences"
+ description: "Check config to show/hide the private space search illustration and search tile content in Hide Private Space settings page"
+ bug: "346612477"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 811a834..7353dde 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -37,6 +37,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -470,6 +471,9 @@
// Set to PowerManager.BRIGHTNESS_INVALID if there's no override.
public float screenBrightnessOverride;
+ // Tag used to identify the app window requesting the brightness override.
+ public CharSequence screenBrightnessOverrideTag;
+
// An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to
// 1 (brighter). Set to Float.NaN if there's no override.
public float screenAutoBrightnessAdjustmentOverride;
@@ -524,6 +528,7 @@
policy = other.policy;
useProximitySensor = other.useProximitySensor;
screenBrightnessOverride = other.screenBrightnessOverride;
+ screenBrightnessOverrideTag = other.screenBrightnessOverrideTag;
screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
blockScreenOn = other.blockScreenOn;
@@ -544,8 +549,9 @@
return other != null
&& policy == other.policy
&& useProximitySensor == other.useProximitySensor
- && floatEquals(screenBrightnessOverride,
- other.screenBrightnessOverride)
+ && floatEquals(screenBrightnessOverride, other.screenBrightnessOverride)
+ && Objects.equals(screenBrightnessOverrideTag,
+ other.screenBrightnessOverrideTag)
&& floatEquals(screenAutoBrightnessAdjustmentOverride,
other.screenAutoBrightnessAdjustmentOverride)
&& screenLowPowerBrightnessFactor
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index df353e5..ce3156e 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -104,8 +104,10 @@
* This method must only be called by the window manager.
*
* @param brightness The overridden brightness, or Float.NaN to disable the override.
+ * @param tag Source identifier of the app window that requests the override.
*/
- public abstract void setScreenBrightnessOverrideFromWindowManager(float brightness);
+ public abstract void setScreenBrightnessOverrideFromWindowManager(
+ float brightness, CharSequence tag);
/**
* Used by the window manager to override the user activity timeout based on the
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index b1ef05a..6a2daea 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -1102,7 +1102,6 @@
/**
* Instructs the zygote to pre-load the application code for the given Application.
* Only the app zygote supports this function.
- * TODO preloadPackageForAbi() can probably be removed and the callers an use this instead.
*/
public boolean preloadApp(ApplicationInfo appInfo, String abi)
throws ZygoteStartFailedEx, IOException {
@@ -1130,39 +1129,6 @@
}
/**
- * Instructs the zygote to pre-load the classes and native libraries at the given paths
- * for the specified abi. Not all zygotes support this function.
- */
- public boolean preloadPackageForAbi(
- String packagePath, String libsPath, String libFileName, String cacheKey, String abi)
- throws ZygoteStartFailedEx, IOException {
- synchronized (mLock) {
- ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.mZygoteOutputWriter.write("5");
- state.mZygoteOutputWriter.newLine();
-
- state.mZygoteOutputWriter.write("--preload-package");
- state.mZygoteOutputWriter.newLine();
-
- state.mZygoteOutputWriter.write(packagePath);
- state.mZygoteOutputWriter.newLine();
-
- state.mZygoteOutputWriter.write(libsPath);
- state.mZygoteOutputWriter.newLine();
-
- state.mZygoteOutputWriter.write(libFileName);
- state.mZygoteOutputWriter.newLine();
-
- state.mZygoteOutputWriter.write(cacheKey);
- state.mZygoteOutputWriter.newLine();
-
- state.mZygoteOutputWriter.flush();
-
- return (state.mZygoteInputStream.readInt() == 0);
- }
- }
-
- /**
* Instructs the zygote to preload the default set of classes and resources. Returns
* {@code true} if a preload was performed as a result of this call, and {@code false}
* otherwise. The latter usually means that the zygote eagerly preloaded at startup
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ff38920..850b979 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2630,7 +2630,7 @@
/**
* Activity Action: Show screen that let user select its Autofill Service.
* <p>
- * Input: Intent's data URI set with an application name, using the
+ * Input: Intent's data URI set with an application package name, using the
* "package" schema (like "package:com.my.app").
*
* <p>
@@ -2650,7 +2650,7 @@
/**
* Activity Action: Show screen that let user enable a Credential Manager provider.
* <p>
- * Input: Intent's data URI set with an application name, using the
+ * Input: Intent's data URI set with an application package name, using the
* "package" schema (like "package:com.my.app").
*
* <p>
diff --git a/core/java/android/service/notification/SystemZenRules.java b/core/java/android/service/notification/SystemZenRules.java
index 22234a9..1d18643 100644
--- a/core/java/android/service/notification/SystemZenRules.java
+++ b/core/java/android/service/notification/SystemZenRules.java
@@ -129,10 +129,7 @@
}
sb.append(daysSummary);
sb.append(context.getString(R.string.zen_mode_trigger_summary_divider_text));
- sb.append(context.getString(
- R.string.zen_mode_trigger_summary_range_symbol_combination,
- timeString(context, schedule.startHour, schedule.startMinute),
- timeString(context, schedule.endHour, schedule.endMinute)));
+ sb.append(getTimeSummary(context, schedule));
return sb.toString();
}
@@ -142,7 +139,7 @@
* adjacent days grouped together ("Sun-Wed" instead of "Sun,Mon,Tue,Wed").
*/
@Nullable
- private static String getShortDaysSummary(Context context, @NonNull ScheduleInfo schedule) {
+ public static String getShortDaysSummary(Context context, @NonNull ScheduleInfo schedule) {
// Compute a list of days with contiguous days grouped together, for example: "Sun-Thu" or
// "Sun-Mon,Wed,Fri"
final int[] days = schedule.days;
@@ -224,6 +221,14 @@
return null;
}
+ /** Returns the time part of a {@link ScheduleInfo}, e.g. {@code 9:00-17:00}. */
+ public static String getTimeSummary(Context context, @NonNull ScheduleInfo schedule) {
+ return context.getString(
+ R.string.zen_mode_trigger_summary_range_symbol_combination,
+ timeString(context, schedule.startHour, schedule.startMinute),
+ timeString(context, schedule.endHour, schedule.endMinute));
+ }
+
/**
* Convenience method for representing the specified time in string format.
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a23e383..64c7766 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11004,6 +11004,11 @@
? afm.isAutofillable(this) : false;
}
+ /**
+ * Returns whether the view is autofillable.
+ *
+ * @return whether the view is autofillable, and should send out autofill request to provider.
+ */
private boolean isAutofillable() {
if (DBG) {
Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
@@ -27631,6 +27636,23 @@
return null;
}
+
+ /**
+ * Performs the traversal to find views that are autofillable.
+ * Autofillable views are added to the provided list.
+ *
+ * <strong>Note:</strong>This method does not stop at the root namespace
+ * boundary.
+ *
+ * @param autofillableViews The output list of autofillable Views.
+ * @hide
+ */
+ public void findAutofillableViewsByTraversal(@NonNull List<View> autofillableViews) {
+ if (isAutofillable()) {
+ autofillableViews.add(this);
+ }
+ }
+
/**
* Look for a child view with the given tag. If this view has the given
* tag, return this view.
@@ -30597,6 +30619,8 @@
}
}
+ // Note that if the function returns true, it indicates aapt did not generate this id.
+ // However false value does not indicate that aapt did generated this id.
private static boolean isViewIdGenerated(int id) {
return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index ceaca22..6f88386 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1500,6 +1500,19 @@
return null;
}
+ /** @hide */
+ @Override
+ public void findAutofillableViewsByTraversal(@NonNull List<View> autofillableViews) {
+ super.findAutofillableViewsByTraversal(autofillableViews);
+
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ child.findAutofillableViewsByTraversal(autofillableViews);
+ }
+ }
+
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 37d5220..9e52a14 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -12900,11 +12900,6 @@
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
}
- // If it's currently an intermittent update,
- // we should keep mPreferredFrameRateCategory as NORMAL
- if (intermittentUpdateState() == INTERMITTENT_STATE_INTERMITTENT) {
- return;
- }
if (mFrameRateCategoryHighCount > 0) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index 2f7adaa..d505c733 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -39,6 +39,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* A controller to manage the autofill requests for the {@link Activity}.
@@ -71,6 +72,7 @@
private AutofillPopupWindow mAutofillPopupWindow;
private boolean mAutoFillResetNeeded;
private boolean mAutoFillIgnoreFirstResumePause;
+ private Boolean mRelayoutFix;
/**
* AutofillClientController constructor.
@@ -86,6 +88,18 @@
return mAutofillManager;
}
+ /**
+ * Whether to apply relayout fixes.
+ *
+ * @hide
+ */
+ public boolean isRelayoutFixEnabled() {
+ if (mRelayoutFix == null) {
+ mRelayoutFix = getAutofillManager().isRelayoutFixEnabled();
+ }
+ return mRelayoutFix;
+ }
+
// ------------------ Called for Activity events ------------------
/**
@@ -119,7 +133,54 @@
* Called when the {@link Activity#onResume()} is called.
*/
public void onActivityResumed() {
+ if (Helper.sVerbose) {
+ Log.v(TAG, "onActivityResumed()");
+ }
+ if (isRelayoutFixEnabled()) {
+ // Do nothing here. We'll handle it in onActivityPostResumed()
+ return;
+ }
+ if (Helper.sVerbose) {
+ Log.v(TAG, "onActivityResumed(): Relayout fix not enabled");
+ }
+ forResume();
+ }
+
+ /**
+ * Called when the {@link Activity#onPostResume()} is called.
+ */
+ public void onActivityPostResumed() {
+ if (Helper.sVerbose) {
+ Log.v(TAG, "onActivityPostResumed()");
+ }
+ if (!isRelayoutFixEnabled()) {
+ return;
+ }
+ if (Helper.sVerbose) {
+ Log.v(TAG, "onActivityPostResumed(): Relayout fix enabled");
+ }
+ forResume();
+ }
+
+ /**
+ * Code to execute when an app has resumed (or is about to resume)
+ */
+ private void forResume() {
enableAutofillCompatibilityIfNeeded();
+ boolean relayoutFix = isRelayoutFixEnabled();
+ if (relayoutFix) {
+ if (getAutofillManager().shouldRetryFill()) {
+ if (Helper.sVerbose) {
+ Log.v(TAG, "forResume(): Autofill potential relayout. Retrying fill.");
+ }
+ getAutofillManager().attemptRefill();
+ } else {
+ if (Helper.sVerbose) {
+ Log.v(TAG, "forResume(): Not attempting refill.");
+ }
+ }
+ }
+
if (mAutoFillResetNeeded) {
if (!mAutoFillIgnoreFirstResumePause) {
View focus = mActivity.getCurrentFocus();
@@ -131,7 +192,16 @@
// ViewRootImpl.performTraversals() changes window visibility to VISIBLE.
// So we cannot call View.notifyEnterOrExited() which will do nothing
// when View.isVisibleToUser() is false.
- getAutofillManager().notifyViewEntered(focus);
+ if (relayoutFix && getAutofillManager().isAuthenticationPending()) {
+ if (Helper.sVerbose) {
+ Log.v(TAG, "forResume(): ignoring focus due to auth pending");
+ }
+ } else {
+ if (Helper.sVerbose) {
+ Log.v(TAG, "forResume(): notifyViewEntered");
+ }
+ getAutofillManager().notifyViewEntered(focus);
+ }
}
}
}
@@ -427,6 +497,22 @@
}
@Override
+ public List<View> autofillClientFindAutofillableViewsByTraversal() {
+ final ArrayList<View> views = new ArrayList<>();
+ final ArrayList<ViewRootImpl> roots =
+ WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
+
+ for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+ final View rootView = roots.get(rootNum).getView();
+
+ if (rootView != null) {
+ rootView.findAutofillableViewsByTraversal(views);
+ }
+ }
+ return views;
+ }
+
+ @Override
public boolean autofillClientIsFillUiShowing() {
return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 515ed0e..02a86c9 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -874,6 +874,13 @@
@Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);
/**
+ * Finds all the autofillable views on the screen.
+ *
+ * @return The list of views that are autofillable.
+ */
+ List<View> autofillClientFindAutofillableViewsByTraversal();
+
+ /**
* Runs the specified action on the UI thread.
*/
void autofillClientRunOnUiThread(Runnable action);
@@ -1498,6 +1505,37 @@
}
/**
+ * Called to know whether authentication was pending.
+ * @hide
+ */
+ public boolean isAuthenticationPending() {
+ return mState == STATE_PENDING_AUTHENTICATION;
+ }
+
+ /**
+ * Called to check if we should retry fill.
+ * Useful for knowing whether to attempt refill after relayout.
+ *
+ * @hide
+ */
+ public boolean shouldRetryFill() {
+ // TODO: Implement in follow-up cl
+ return false;
+ }
+
+ /**
+ * Called when a potential relayout may have occurred.
+ *
+ * @return whether refill was done. True if refill was done partially or fully.
+ * @hide
+ */
+ public boolean attemptRefill() {
+ Log.i(TAG, "Attempting refill");
+ // TODO: Implement in follow-up cl
+ return false;
+ }
+
+ /**
* Called when a {@link View} that supports autofill is entered.
*
* @param view {@link View} that was entered.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0dadbe3..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -8360,7 +8360,7 @@
}
}
- private interface PendingResources<T> {
+ interface PendingResources<T> {
T create(Context context, Resources appResources, HierarchyRootData rootData, int depth)
throws Exception;
}
diff --git a/core/java/android/widget/RemoteViewsSerializers.java b/core/java/android/widget/RemoteViewsSerializers.java
new file mode 100644
index 0000000..600fea4
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsSerializers.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 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.widget;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Icon;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.util.function.Function;
+
+/**
+ * This class provides serialization for certain types used within RemoteViews.
+ *
+ * @hide
+ */
+public class RemoteViewsSerializers {
+ private static final String TAG = "RemoteViews";
+
+ /**
+ * Write Icon to proto.
+ */
+ public static void writeIconToProto(@NonNull ProtoOutputStream out,
+ @NonNull Resources appResources, @NonNull Icon icon) {
+ if (icon.getTintList() != null) {
+ final long token = out.start(RemoteViewsProto.Icon.TINT_LIST);
+ icon.getTintList().writeToProto(out);
+ out.end(token);
+ }
+ out.write(RemoteViewsProto.Icon.BLEND_MODE, BlendMode.toValue(icon.getTintBlendMode()));
+ switch (icon.getType()) {
+ case Icon.TYPE_BITMAP:
+ final ByteArrayOutputStream bitmapBytes = new ByteArrayOutputStream();
+ icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bitmapBytes);
+ out.write(RemoteViewsProto.Icon.BITMAP, bitmapBytes.toByteArray());
+ break;
+ case Icon.TYPE_ADAPTIVE_BITMAP:
+ final ByteArrayOutputStream adaptiveBitmapBytes = new ByteArrayOutputStream();
+ icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100,
+ adaptiveBitmapBytes);
+ out.write(RemoteViewsProto.Icon.ADAPTIVE_BITMAP, adaptiveBitmapBytes.toByteArray());
+ break;
+ case Icon.TYPE_RESOURCE:
+ out.write(RemoteViewsProto.Icon.RESOURCE,
+ appResources.getResourceName(icon.getResId()));
+ break;
+ case Icon.TYPE_DATA:
+ out.write(RemoteViewsProto.Icon.DATA, icon.getDataBytes());
+ break;
+ case Icon.TYPE_URI:
+ out.write(RemoteViewsProto.Icon.URI, icon.getUriString());
+ break;
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
+ out.write(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP, icon.getUriString());
+ break;
+ default:
+ Log.e(TAG, "Tried to serialize unknown Icon type " + icon.getType());
+ }
+ }
+
+ /**
+ * Create Icon from proto.
+ */
+ @NonNull
+ public static Function<Resources, Icon> createIconFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.Icon.BLEND_MODE:
+ values.put(RemoteViewsProto.Icon.BLEND_MODE,
+ in.readInt(RemoteViewsProto.Icon.BLEND_MODE));
+ break;
+ case (int) RemoteViewsProto.Icon.TINT_LIST:
+ final long tintListToken = in.start(RemoteViewsProto.Icon.TINT_LIST);
+ values.put(RemoteViewsProto.Icon.TINT_LIST, ColorStateList.createFromProto(in));
+ in.end(tintListToken);
+ break;
+ case (int) RemoteViewsProto.Icon.BITMAP:
+ byte[] bitmapData = in.readBytes(RemoteViewsProto.Icon.BITMAP);
+ values.put(RemoteViewsProto.Icon.BITMAP,
+ BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length));
+ break;
+ case (int) RemoteViewsProto.Icon.ADAPTIVE_BITMAP:
+ final byte[] bitmapAdaptiveData = in.readBytes(
+ RemoteViewsProto.Icon.ADAPTIVE_BITMAP);
+ values.put(RemoteViewsProto.Icon.ADAPTIVE_BITMAP,
+ BitmapFactory.decodeByteArray(bitmapAdaptiveData, 0,
+ bitmapAdaptiveData.length));
+ break;
+ case (int) RemoteViewsProto.Icon.RESOURCE:
+ values.put(RemoteViewsProto.Icon.RESOURCE,
+ in.readString(RemoteViewsProto.Icon.RESOURCE));
+ break;
+ case (int) RemoteViewsProto.Icon.DATA:
+ values.put(RemoteViewsProto.Icon.DATA,
+ in.readBytes(RemoteViewsProto.Icon.DATA));
+ break;
+ case (int) RemoteViewsProto.Icon.URI:
+ values.put(RemoteViewsProto.Icon.URI, in.readString(RemoteViewsProto.Icon.URI));
+ break;
+ case (int) RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP:
+ values.put(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
+ in.readString(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP));
+ break;
+ default:
+ Log.w(TAG, "Unhandled field while reading Icon proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+
+ return (resources) -> {
+ final int blendMode = (int) values.get(RemoteViewsProto.Icon.BLEND_MODE, -1);
+ final ColorStateList tintList = (ColorStateList) values.get(
+ RemoteViewsProto.Icon.TINT_LIST);
+ final Bitmap bitmap = (Bitmap) values.get(RemoteViewsProto.Icon.BITMAP);
+ final Bitmap bitmapAdaptive = (Bitmap) values.get(
+ RemoteViewsProto.Icon.ADAPTIVE_BITMAP);
+ final String resName = (String) values.get(RemoteViewsProto.Icon.RESOURCE);
+ final int resource = resName != null ? resources.getIdentifier(resName, /* defType= */
+ null,
+ /* defPackage= */ null) : -1;
+ final byte[] data = (byte[]) values.get(RemoteViewsProto.Icon.DATA);
+ final String uri = (String) values.get(RemoteViewsProto.Icon.URI);
+ final String uriAdaptive = (String) values.get(
+ RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP);
+ Icon icon;
+ if (bitmap != null) {
+ icon = Icon.createWithBitmap(bitmap);
+ } else if (bitmapAdaptive != null) {
+ icon = Icon.createWithAdaptiveBitmap(bitmapAdaptive);
+ } else if (resource != -1) {
+ icon = Icon.createWithResource(resources, resource);
+ } else if (data != null) {
+ icon = Icon.createWithData(data, 0, data.length);
+ } else if (uri != null) {
+ icon = Icon.createWithContentUri(uri);
+ } else if (uriAdaptive != null) {
+ icon = Icon.createWithAdaptiveBitmapContentUri(uriAdaptive);
+ } else {
+ // Either this Icon has no data or is of an unknown type.
+ return null;
+ }
+
+ if (tintList != null) {
+ icon.setTintList(tintList);
+ }
+ if (blendMode != -1) {
+ icon.setTintBlendMode(BlendMode.fromValue(blendMode));
+ }
+ return icon;
+ };
+ }
+}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 9ed7384..8dee223 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -16,18 +16,15 @@
package com.android.internal.os;
-import android.app.ApplicationLoaders;
import android.app.LoadedApk;
import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
-import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
import android.webkit.WebViewLibraryLoader;
import java.io.DataOutputStream;
-import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -83,28 +80,6 @@
Log.i(TAG, "Application preload done");
}
- @Override
- protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
- String cacheKey) {
- Log.i(TAG, "Beginning package preload");
- // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
- // our children will reuse the same classloader instead of creating their own.
- // This enables us to preload Java and native code in the webview zygote process and
- // have the preloaded versions actually be used post-fork.
- ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
- packagePath, libsPath, cacheKey);
-
- // Add the APK to the Zygote's list of allowed files for children.
- String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
- for (String packageEntry : packageList) {
- Zygote.nativeAllowFileAcrossFork(packageEntry);
- }
-
- doPreload(loader, libFileName);
-
- Log.i(TAG, "Package preload done");
- }
-
private void doPreload(ClassLoader loader, String libFileName) {
// Load the native library using WebViewLibraryLoader to share the RELRO data with other
// processes.
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index cab84bb..fafa085 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -938,8 +938,6 @@
throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--get-pid");
} else if (args.mPreloadDefault) {
throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-default");
- } else if (args.mPreloadPackage != null) {
- throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-package");
} else if (args.mPreloadApp != null) {
throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app");
} else if (args.mStartChildZygote) {
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 86b9a59..27ce64e 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -141,33 +141,12 @@
String mAppDataDir;
/**
- * The APK path of the package to preload, when using --preload-package.
- */
- String mPreloadPackage;
-
- /**
* A Base64 string representing a serialize ApplicationInfo Parcel,
when using --preload-app.
*/
String mPreloadApp;
/**
- * The native library path of the package to preload, when using --preload-package.
- */
- String mPreloadPackageLibs;
-
- /**
- * The filename of the native library to preload, when using --preload-package.
- */
- String mPreloadPackageLibFileName;
-
- /**
- * The cache key under which to enter the preloaded package into the classloader cache, when
- * using --preload-package.
- */
- String mPreloadPackageCacheKey;
-
- /**
* Whether this is a request to start preloading the default resources and classes. This
* argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
* with --enable-lazy-preload).
@@ -419,12 +398,6 @@
} else if (arg.equals("--preload-app")) {
++curArg;
mPreloadApp = args.nextArg();
- } else if (arg.equals("--preload-package")) {
- curArg += 4;
- mPreloadPackage = args.nextArg();
- mPreloadPackageLibs = args.nextArg();
- mPreloadPackageLibFileName = args.nextArg();
- mPreloadPackageCacheKey = args.nextArg();
} else if (arg.equals("--preload-default")) {
mPreloadDefault = true;
expectRuntimeArgs = false;
@@ -504,11 +477,6 @@
if (argCount > curArg) {
throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
}
- } else if (mPreloadPackage != null) {
- if (argCount > curArg) {
- throw new IllegalArgumentException(
- "Unexpected arguments after --preload-package.");
- }
} else if (mPreloadApp != null) {
if (argCount > curArg) {
throw new IllegalArgumentException(
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index d4dcec9..2eab081 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -165,14 +165,6 @@
return null;
}
- if (parsedArgs.mPreloadPackage != null) {
- handlePreloadPackage(parsedArgs.mPreloadPackage,
- parsedArgs.mPreloadPackageLibs,
- parsedArgs.mPreloadPackageLibFileName,
- parsedArgs.mPreloadPackageCacheKey);
- return null;
- }
-
if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
Parcel appInfoParcel = Parcel.obtain();
@@ -475,11 +467,6 @@
return mSocketOutStream;
}
- protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
- String cacheKey) {
- throw new RuntimeException("Zygote does not support package preloading");
- }
-
protected boolean canPreloadApp() {
return false;
}
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index f08ea1b..37d1c5b 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -21,6 +21,7 @@
package android.widget;
import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/content/res/color_state_list.proto";
/**
* An android.widget.RemoteViews object. This is used by RemoteViews.createPreviewFromProto
@@ -71,6 +72,23 @@
optional int32 view_type_count = 4;
optional bool attached = 5;
}
+
+ /**
+ * An android.graphics.drawable Icon.
+ */
+ message Icon {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 blend_mode = 1;
+ optional android.content.res.ColorStateListProto tint_list = 2;
+ oneof icon {
+ bytes bitmap = 3;
+ string resource = 4;
+ bytes data = 5;
+ string uri = 6;
+ string uri_adaptive_bitmap = 7;
+ bytes adaptive_bitmap = 8;
+ };
+ }
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 236e7c5..8cb7646 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7069,6 +7069,9 @@
<!-- Whether desktop mode is supported on the current device -->
<bool name="config_isDesktopModeSupported">false</bool>
+ <!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
+ <integer name="config_maxDesktopWindowingActiveTasks">0</integer>
+
<!-- Frame rate compatibility value for Wallpaper
FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption -->
<integer name="config_wallpaperFrameRateCompatibility">102</integer>
@@ -7093,4 +7096,8 @@
<!-- Whether to enable usb state update via udc sysfs. -->
<bool name="config_enableUdcSysfsUsbStateUpdate">false</bool>
+
+ <!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page.
+ OEM/Partner can explicitly opt to hide the illustration and search tile content. -->
+ <bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 09688f2..4a425cb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -524,6 +524,7 @@
<java-symbol type="bool" name="config_notificationCloseButtonSupported"/>
<java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/>
<java-symbol type="bool" name="config_enableUdcSysfsUsbStateUpdate"/>
+ <java-symbol type="bool" name="config_enableSearchTileHideIllustrationInPrivateSpace"/>
<java-symbol type="color" name="tab_indicator_text_v4" />
@@ -5522,6 +5523,9 @@
<!-- Whether desktop mode is supported on the current device -->
<java-symbol type="bool" name="config_isDesktopModeSupported" />
+ <!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
+ <java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
+
<!-- Frame rate compatibility value for Wallpaper -->
<java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index f5affd3..4f5107a 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -983,7 +983,7 @@
new MergedConfiguration(currentConfig, currentConfig),
false /* preserveWindow */, activityWindowInfo);
final ResumeActivityItem resumeStateRequest =
- ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
+ new ResumeActivityItem(activity.getActivityToken(), true /* isForward */,
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
@@ -996,7 +996,7 @@
@NonNull
private static ClientTransaction newResumeTransaction(@NonNull Activity activity) {
final ResumeActivityItem resumeStateRequest =
- ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
+ new ResumeActivityItem(activity.getActivityToken(), true /* isForward */,
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index ee7fa7c..911b759 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -21,13 +21,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
import android.content.res.Configuration;
@@ -102,8 +106,8 @@
@Test
public void testDestroyActivityItem_preExecute() {
- final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */);
+ final DestroyActivityItem item =
+ new DestroyActivityItem(mActivityToken, false /* finished */);
item.preExecute(mHandler);
@@ -113,8 +117,8 @@
@Test
public void testDestroyActivityItem_postExecute() {
- final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */);
+ final DestroyActivityItem item =
+ new DestroyActivityItem(mActivityToken, false /* finished */);
item.preExecute(mHandler);
item.postExecute(mHandler, mPendingActions);
@@ -124,8 +128,8 @@
@Test
public void testDestroyActivityItem_execute() {
- final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */);
+ final DestroyActivityItem item =
+ new DestroyActivityItem(mActivityToken, false /* finished */);
item.execute(mHandler, mActivityClientRecord, mPendingActions);
@@ -134,6 +138,41 @@
}
@Test
+ public void testResumeActivityItem_preExecute_withProcState_updatesProcessState() {
+ final ResumeActivityItem item = new ResumeActivityItem(mActivityToken,
+ ActivityManager.PROCESS_STATE_TOP /* procState */,
+ true /* isForward */,
+ false /* shouldSendCompatFakeFocus*/);
+
+ item.preExecute(mHandler);
+
+ verify(mHandler).updateProcessState(ActivityManager.PROCESS_STATE_TOP, false);
+ }
+
+ @Test
+ public void testResumeActivityItem_preExecute_withUnknownProcState_skipsProcessStateUpdate() {
+ final ResumeActivityItem item = new ResumeActivityItem(mActivityToken,
+ ActivityManager.PROCESS_STATE_UNKNOWN /* procState */,
+ true /* isForward */,
+ false /* shouldSendCompatFakeFocus*/);
+
+ item.preExecute(mHandler);
+
+ verify(mHandler, never()).updateProcessState(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testResumeActivityItem_preExecute_withoutProcState_skipsProcessStateUpdate() {
+ final ResumeActivityItem item = new ResumeActivityItem(mActivityToken,
+ true /* isForward */,
+ false /* shouldSendCompatFakeFocus*/);
+
+ item.preExecute(mHandler);
+
+ verify(mHandler, never()).updateProcessState(anyInt(), anyBoolean());
+ }
+
+ @Test
public void testWindowContextInfoChangeItem_execute() {
final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
.obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index da88478..1817b87 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -90,11 +90,6 @@
}
@Test
- public void testRecycleDestroyActivityItem() {
- testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true));
- }
-
- @Test
public void testRecycleLaunchActivityItem() {
final IBinder activityToken = new Binder();
final Intent intent = new Intent("action");
@@ -166,11 +161,6 @@
}
@Test
- public void testRecycleResumeActivityItem() {
- testRecycle(() -> ResumeActivityItem.obtain(mActivityToken, 3, true, false));
- }
-
- @Test
public void testRecycleStartActivityItem() {
testRecycle(() -> StartActivityItem.obtain(mActivityToken,
new ActivityOptions.SceneTransitionInfo()));
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index ad86e75..c2d56b6 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -275,7 +275,7 @@
final IBinder token = mock(IBinder.class);
final ClientTransaction destroyTransaction = new ClientTransaction();
destroyTransaction.addTransactionItem(
- DestroyActivityItem.obtain(token, false /* finished */));
+ new DestroyActivityItem(token, false /* finished */));
destroyTransaction.preExecute(mTransactionHandler);
// The activity should be added to to-be-destroyed container.
assertEquals(1, activitiesToBeDestroyed.size());
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index e995035..dde6ab1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -147,11 +147,12 @@
@Test
public void testDestroy() {
- DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */);
+ final DestroyActivityItem item =
+ new DestroyActivityItem(mActivityToken, true /* finished */);
writeAndPrepareForReading(item);
// Read from parcel and assert
- DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
+ final DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
@@ -248,12 +249,12 @@
@Test
public void testResume() {
// Write to parcel
- ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 27 /* procState */,
+ final ResumeActivityItem item = new ResumeActivityItem(mActivityToken, 27 /* procState */,
true /* isForward */, false /* shouldSendCompatFakeFocus */);
writeAndPrepareForReading(item);
// Read from parcel and assert
- ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
+ final ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index bce3f3e..ae3ad36 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -19,6 +19,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -29,11 +30,16 @@
import android.content.Context;
import android.graphics.Region;
import android.platform.test.annotations.Presubmit;
+import android.view.autofill.AutofillId;
import androidx.test.filters.SmallTest;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Test basic functions of ViewGroup.
@@ -182,6 +188,31 @@
assertRegionContainPoint(3 /* x */, r, false /* contain */); // Outside of bounds
}
+ @Test
+ public void testfindAutofillableViewsByTraversal() {
+ final Context context = getInstrumentation().getContext();
+ final TestView viewGroup = new TestView(context, 200 /* right */);
+
+ // viewA and viewC are autofillable. ViewB isn't.
+ final TestView viewA = spy(new AutofillableTestView(context, 100 /* right */));
+ final TestView viewB = spy(new NonAutofillableTestView(context, 200 /* right */));
+ final TestView viewC = spy(new AutofillableTestView(context, 300 /* right */));
+
+ viewGroup.addView(viewA);
+ viewGroup.addView(viewB);
+ viewGroup.addView(viewC);
+
+ List<View> autofillableViews = new ArrayList<>();
+ viewGroup.findAutofillableViewsByTraversal(autofillableViews);
+
+ verify(viewA).findAutofillableViewsByTraversal(autofillableViews);
+ verify(viewB).findAutofillableViewsByTraversal(autofillableViews);
+ verify(viewC).findAutofillableViewsByTraversal(autofillableViews);
+
+ assertEquals("Size of autofillable views", 2, autofillableViews.size());
+ assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
+ }
+
private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
final ViewParent parent = view.getParent();
@@ -210,4 +241,29 @@
// We don't layout this view.
}
}
+
+ public static class AutofillableTestView extends TestView {
+ AutofillableTestView(Context context, int right) {
+ super(context, right);
+ setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
+ // Need to set autofill id in such a way that the view is considered part of activity.
+ setAutofillId(new AutofillId(LAST_APP_AUTOFILL_ID + 5));
+ }
+
+ @Override
+ public @AutofillType int getAutofillType() {
+ return AUTOFILL_TYPE_TEXT;
+ }
+ }
+
+ public static class NonAutofillableTestView extends TestView {
+ NonAutofillableTestView(Context context, int right) {
+ super(context, right);
+ }
+
+ @Override
+ public @AutofillType int getAutofillType() {
+ return AUTOFILL_TYPE_NONE;
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
new file mode 100644
index 0000000..44d10d3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.widget
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Icon
+import android.util.proto.ProtoInputStream
+import android.util.proto.ProtoOutputStream
+import android.widget.RemoteViewsSerializers.createIconFromProto
+import android.widget.RemoteViewsSerializers.writeIconToProto
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.frameworks.coretests.R
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayOutputStream
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RemoteViewsSerializersTest {
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ /**
+ * Based on android.graphics.drawable.IconTest#testParcel
+ */
+ @Test
+ fun testWriteIconToProto() {
+ val bitmap = (context.getDrawable(R.drawable.landscape) as BitmapDrawable).bitmap
+ val bitmapData = ByteArrayOutputStream().let {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
+ it.toByteArray()
+ }
+
+ for (icon in listOf(
+ Icon.createWithBitmap(bitmap),
+ Icon.createWithAdaptiveBitmap(bitmap),
+ Icon.createWithData(bitmapData, 0, bitmapData.size),
+ Icon.createWithResource(context, R.drawable.landscape),
+ Icon.createWithContentUri("content://com.example.myapp/my_icon"),
+ Icon.createWithAdaptiveBitmapContentUri("content://com.example.myapp/my_icon"),
+ )) {
+ icon.tintList = ColorStateList.valueOf(Color.RED)
+ icon.tintBlendMode = BlendMode.SRC_OVER
+ val bytes = ProtoOutputStream().let {
+ writeIconToProto(it, context.resources, icon)
+ it.bytes
+ }
+
+ val copy = ProtoInputStream(bytes).let {
+ createIconFromProto(it).apply(context.resources)
+ }
+ assertThat(copy.type).isEqualTo(icon.type)
+ assertThat(copy.tintBlendMode).isEqualTo(icon.tintBlendMode)
+ assertThat(equalColorStateLists(copy.tintList, icon.tintList)).isTrue()
+
+ when (icon.type) {
+ Icon.TYPE_DATA, Icon.TYPE_URI, Icon.TYPE_URI_ADAPTIVE_BITMAP,
+ Icon.TYPE_RESOURCE -> {
+ assertThat(copy.sameAs(icon)).isTrue()
+ }
+
+ Icon.TYPE_BITMAP, Icon.TYPE_ADAPTIVE_BITMAP -> {
+ assertThat(copy.bitmap.sameAs(icon.bitmap)).isTrue()
+ }
+ }
+ }
+ }
+}
+
+fun equalColorStateLists(a: ColorStateList?, b: ColorStateList?): Boolean {
+ if (a == null && b == null) return true
+ return a != null && b != null &&
+ a.colors.contentEquals(b.colors) &&
+ a.states.foldIndexed(true) { i, acc, it -> acc && it.contentEquals(b.states[i])}
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index a1ba24c..282385a 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -89,20 +89,15 @@
private static final int DESKTOP_DENSITY_MAX = 1000;
/**
- * Default value for {@code MAX_TASK_LIMIT}.
- */
- @VisibleForTesting
- public static final int DEFAULT_MAX_TASK_LIMIT = 4;
-
- // TODO(b/335131008): add a config-overlay field for the max number of tasks in Desktop Mode
- /**
- * Flag declaring the maximum number of Tasks to show in Desktop Mode at any one time.
+ * Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time.
*
- * <p> The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen
+ * <p>If it is not defined, then {@code R.integer.config_maxDesktopWindowingActiveTasks} is
+ * used.
+ *
+ * <p>The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen
* recording window, or Bluetooth pairing window).
*/
- private static final int MAX_TASK_LIMIT = SystemProperties.getInt(
- "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);
+ private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit";
/**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
@@ -139,8 +134,9 @@
/**
* Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time.
*/
- public static int getMaxTaskLimit() {
- return MAX_TASK_LIMIT;
+ public static int getMaxTaskLimit(@NonNull Context context) {
+ return SystemProperties.getInt(MAX_TASK_LIMIT_SYS_PROP,
+ context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks));
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index fca8a62..949a723 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -734,6 +734,9 @@
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
if (canShowAsBubbleBar()) {
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
+ if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
+ mLayerView.updateExpandedView();
+ }
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fa1091c..d45ed0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -38,6 +38,9 @@
var isStuckToDismiss: Boolean = false
private set
+ var isDragged: Boolean = false
+ private set
+
private var expandedViewInitialTranslationX = 0f
private var expandedViewInitialTranslationY = 0f
private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
@@ -94,6 +97,7 @@
// While animating, don't allow new touch events
if (expandedView.isAnimating) return false
pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
+ isDragged = true
return true
}
@@ -141,6 +145,7 @@
dismissView.hide()
}
isMoving = false
+ isDragged = false
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index badc409..9fa85cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -175,6 +175,11 @@
return mIsExpanded;
}
+ /** Return whether the expanded view is being dragged */
+ public boolean isExpandedViewDragged() {
+ return mDragController != null && mDragController.isDragged();
+ }
+
/** Shows the expanded view of the provided bubble. */
public void showExpandedView(BubbleViewProvider b) {
BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 8f587d4..afe46f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -567,13 +567,15 @@
Transitions transitions,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
ShellTaskOrganizer shellTaskOrganizer) {
+ int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
if (!DesktopModeStatus.canEnterDesktopMode(context)
- || DESKTOP_WINDOWING_MODE.isEnabled(context)) {
+ || DESKTOP_WINDOWING_MODE.isEnabled(context)
+ || maxTaskLimit <= 0) {
return Optional.empty();
}
return Optional.of(
new DesktopTasksLimiter(
- transitions, desktopModeTaskRepository, shellTaskOrganizer));
+ transitions, desktopModeTaskRepository, shellTaskOrganizer, maxTaskLimit));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 534cc22..c5ed1be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -26,7 +26,6 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
@@ -40,12 +39,17 @@
transitions: Transitions,
private val taskRepository: DesktopModeTaskRepository,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val maxTasksLimit: Int,
) {
private val minimizeTransitionObserver = MinimizeTransitionObserver()
@VisibleForTesting
val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
init {
+ require(maxTasksLimit > 0) {
+ "DesktopTasksLimiter should not be created with a maxTasksLimit at 0 or less. " +
+ "Current value: $maxTasksLimit."
+ }
transitions.registerObserver(minimizeTransitionObserver)
taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
}
@@ -194,12 +198,6 @@
}
/**
- * Returns the maximum number of tasks that should ever be displayed at the same time in Desktop
- * Mode.
- */
- fun getMaxTaskLimit(): Int = DesktopModeStatus.getMaxTaskLimit()
-
- /**
* Returns the Task to minimize given 1. a list of visible tasks ordered from front to back and
* 2. a new task placed in front of all the others.
*/
@@ -216,7 +214,7 @@
fun getTaskToMinimizeIfNeeded(
visibleFreeformTaskIdsOrderedFrontToBack: List<Int>
): RunningTaskInfo? {
- if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) {
+ if (visibleFreeformTaskIdsOrderedFrontToBack.size <= maxTasksLimit) {
ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: no need to minimize; tasks below limit")
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 37510ef4..e66018f 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
@@ -126,11 +126,11 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
-import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -204,7 +204,12 @@
shellInit = spy(ShellInit(testExecutor))
desktopModeTaskRepository = DesktopModeTaskRepository()
desktopTasksLimiter =
- DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
+ DesktopTasksLimiter(
+ transitions,
+ desktopModeTaskRepository,
+ shellTaskOrganizer,
+ MAX_TASK_LIMIT,
+ )
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -780,45 +785,41 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
- assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home
+ assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home
wct.assertReorderAt(0, homeTask)
wct.assertReorderSequenceInRange(
- range = 1..<(taskLimit + 1),
- *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
- newTask
- )
+ range = 1..<(MAX_TASK_LIMIT + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
- assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 2) // tasks + home + wallpaper
+ assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper
// Move home to front
wct.assertReorderAt(0, homeTask)
// Add desktop wallpaper activity
wct.assertPendingIntentAt(1, desktopWallpaperIntent)
// Bring freeform tasks to front
wct.assertReorderSequenceInRange(
- range = 2..<(taskLimit + 2),
- *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
- newTask
- )
+ range = 2..<(MAX_TASK_LIMIT + 2),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask)
}
@Test
@@ -932,9 +933,8 @@
@Test
fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
setUpHomeTask()
- val freeformTasks = (1..taskLimit + 1).map { _ -> setUpFreeformTask() }
+ val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
controller.moveTaskToFront(freeformTasks[0])
@@ -1141,8 +1141,7 @@
fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
@@ -1187,8 +1186,7 @@
fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val newFreeformTask = createFreeformTask()
@@ -2599,9 +2597,10 @@
return TransitionRequestInfo(type, task, null /* remoteTransition */)
}
- companion object {
+ private companion object {
const val SECOND_DISPLAY = 2
- private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+ val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+ const val MAX_TASK_LIMIT = 6
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 8d9fd91..70f3bf8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -38,6 +38,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -77,8 +78,8 @@
desktopTaskRepo = DesktopModeTaskRepository()
- desktopTasksLimiter = DesktopTasksLimiter(
- transitions, desktopTaskRepo, shellTaskOrganizer)
+ desktopTasksLimiter =
+ DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT)
}
@After
@@ -86,12 +87,18 @@
mockitoSession.finishMocking()
}
- // Currently, the task limit can be overridden through an adb flag. This test ensures the limit
- // hasn't been overridden.
@Test
- fun getMaxTaskLimit_isSameAsConstant() {
- assertThat(desktopTasksLimiter.getMaxTaskLimit()).isEqualTo(
- DesktopModeStatus.DEFAULT_MAX_TASK_LIMIT)
+ fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
+ assertFailsWith<IllegalArgumentException> {
+ DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0)
+ }
+ }
+
+ @Test
+ fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
+ assertFailsWith<IllegalArgumentException> {
+ DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5)
+ }
}
@Test
@@ -247,8 +254,7 @@
@Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- (1..<taskLimit).forEach { _ -> setUpFreeformTask() }
+ (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val wct = WindowContainerTransaction()
val minimizedTaskId =
@@ -263,9 +269,8 @@
@Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksAboveLimit_backTaskMinimized() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
// The following list will be ordered bottom -> top, as the last task is moved to top last.
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val wct = WindowContainerTransaction()
val minimizedTaskId =
@@ -282,8 +287,7 @@
@Test
fun addAndGetMinimizeTaskChangesIfNeeded_nonMinimizedTasksWithinLimit_noTaskMinimized() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
val wct = WindowContainerTransaction()
@@ -299,8 +303,7 @@
@Test
fun getTaskToMinimizeIfNeeded_tasksWithinLimit_returnsNull() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
@@ -310,8 +313,7 @@
@Test
fun getTaskToMinimizeIfNeeded_tasksAboveLimit_returnsBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit + 1).map { setUpFreeformTask() }
+ val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
@@ -321,9 +323,21 @@
}
@Test
+ fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() {
+ desktopTasksLimiter =
+ DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2)
+ val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
+
+ val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
+ visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
+
+ // first == front, last == back
+ assertThat(minimizedTask).isEqualTo(tasks.last())
+ }
+
+ @Test
fun getTaskToMinimizeIfNeeded_withNewTask_tasksAboveLimit_returnsBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId },
@@ -358,4 +372,9 @@
visible = false
)
}
+
+ private companion object {
+ const val MAX_TASK_LIMIT = 6
+ const val MAX_TASK_LIMIT2 = 9
+ }
}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2fff4f5..a39f30b 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,9 @@
"-Werror",
"-Wunreachable-code",
],
+ header_libs: [
+ "native_headers",
+ ],
target: {
windows: {
// The Windows compiler warns incorrectly for value initialization with {}.
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index 6744c6c..aebc4db 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -162,8 +162,8 @@
static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
jobject fileDescriptor, jlong length, jboolean preferAnimation, jobject source) {
-#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
- return throw_exception(env, kSourceException, "Only supported on Android", nullptr, source);
+#ifdef _WIN32 // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
+ return throw_exception(env, kSourceException, "Not supported on Windows", nullptr, source);
#else
int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index f64233f..6776f61 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -24,7 +24,6 @@
#include <utils/String8.h>
#include <utils/Thread.h>
-#include <gui/IProducerListener.h>
#include <gui/Surface.h>
#include <ui/PublicFormat.h>
#include <android_runtime/AndroidRuntime.h>
@@ -65,15 +64,18 @@
// ----------------------------------------------------------------------------
-class JNIImageWriterContext : public BnProducerListener {
+class JNIImageWriterContext : public SurfaceListener {
public:
JNIImageWriterContext(JNIEnv* env, jobject weakThiz, jclass clazz);
virtual ~JNIImageWriterContext();
- // Implementation of IProducerListener, used to notify the ImageWriter that the consumer
+ // Implementation of SurfaceListener, used to notify the ImageWriter that the consumer
// has returned a buffer and it is ready for ImageWriter to dequeue.
virtual void onBufferReleased();
+ virtual bool needsReleaseNotify() override { return true; };
+ virtual void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>& /*buffers*/) override {};
+ virtual void onBufferDetached(int /*slot*/) override {};
void setProducer(const sp<Surface>& producer) { mProducer = producer; }
Surface* getProducer() { return mProducer.get(); }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 1c3515a..2f90ccc 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -573,6 +573,7 @@
"lottie_compose",
"LowLightDreamLib",
"TraceurCommon",
+ "Traceur-res",
"//frameworks/libs/systemui:motion_tool_lib",
"notification_flags_lib",
"PlatformComposeCore",
@@ -749,6 +750,7 @@
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
"TraceurCommon",
+ "Traceur-res",
],
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 02e0423..eb9d0ab 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1243,4 +1243,13 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+flag {
+ name: "classic_flags_multi_user"
+ namespace: "systemui"
+ description: "Make the classic feature flag loading multi user aware."
+ bug: "345443431"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
new file mode 100644
index 0000000..01dbc6b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.education.GestureType
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.contextualEducationRepository
+ private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+
+ @Before
+ fun setup() {
+ underTest.start()
+ }
+
+ @Test
+ fun newEducationInfoOnMaxSignalCountReached() =
+ testScope.runTest {
+ tryTriggeringEducation(BACK_GESTURE)
+ val model by collectLastValue(underTest.educationTriggered)
+ assertThat(model?.gestureType).isEqualTo(BACK_GESTURE)
+ }
+
+ @Test
+ fun noEducationInfoBeforeMaxSignalCountReached() =
+ testScope.runTest {
+ repository.incrementSignalCount(BACK_GESTURE)
+ val model by collectLastValue(underTest.educationTriggered)
+ assertThat(model).isNull()
+ }
+
+ @Test
+ fun noEducationInfoWhenShortcutTriggeredPreviously() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ repository.updateShortcutTriggerTime(BACK_GESTURE)
+ tryTriggeringEducation(BACK_GESTURE)
+ assertThat(model).isNull()
+ }
+
+ private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+ // Increment max number of signal to try triggering education
+ for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
+ repository.incrementSignalCount(gestureType)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 6ddc074..7e9f437 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -627,6 +628,7 @@
fakeSettings,
seenNotificationsInteractor,
statusBarStateController,
+ sceneInteractor = kosmos.sceneInteractor,
)
keyguardCoordinator.attach(notifPipeline)
testScope.runTest {
diff --git a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
new file mode 100644
index 0000000..6180bf5
--- /dev/null
+++ b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/categories"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+ <TextView
+ android:id="@+id/cpu_buffer_size"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+ <!-- Attach to Bugreport Switch -->
+ <LinearLayout
+ android:id="@+id/attach_to_bugreport_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/attach_to_bugreport_switch_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="start"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+ <Switch
+ android:id="@+id/attach_to_bugreport_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0" />
+ </LinearLayout>
+
+ <!-- Winscope Switch -->
+ <LinearLayout
+ android:id="@+id/winscope_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/winscope_switch_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="start"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"/>
+
+ <Switch
+ android:id="@+id/winscope_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0" />
+ </LinearLayout>
+
+ <!-- Trace Debuggable Apps Switch -->
+ <LinearLayout
+ android:id="@+id/trace_debuggable_apps_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/debuggable_apps_switch_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="start"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+ <Switch
+ android:id="@+id/trace_debuggable_apps_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0" />
+ </LinearLayout>
+
+ <!-- Long Traces Switch -->
+ <LinearLayout
+ android:id="@+id/long_traces_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/long_traces_switch_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="start"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+ <Switch
+ android:id="@+id/long_traces_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/long_trace_size"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+
+ <TextView
+ android:id="@+id/long_trace_duration"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+</LinearLayout>
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index acc12d7..8146cc5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -948,6 +948,10 @@
<string name="performance">Performance</string>
<string name="user_interface">User Interface</string>
<string name="thermal">Thermal</string>
+ <string name="custom">Custom</string>
+
+ <string name="custom_trace_settings_dialog_title">Custom Trace Settings</string>
+ <string name="restore_default">Restore Default</string>
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_onehanded_label">One-handed mode</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 56de5a3..8ae11ab 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Process;
@@ -81,7 +80,9 @@
public SystemUIApplication() {
super();
- Trace.registerWithPerfetto();
+ if (!isSubprocess()) {
+ Trace.registerWithPerfetto();
+ }
Log.v(TAG, "SystemUIApplication constructed.");
// SysUI may be building without protolog preprocessing in some cases
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
@@ -182,9 +183,7 @@
} else {
// We don't need to startServices for sub-process that is doing some tasks.
// (screenshots, sweetsweetdesserts or tuner ..)
- String processName = ActivityThread.currentProcessName();
- ApplicationInfo info = getApplicationInfo();
- if (processName != null && processName.startsWith(info.processName + ":")) {
+ if (isSubprocess()) {
return;
}
// For a secondary user, boot-completed will never be called because it has already
@@ -195,6 +194,12 @@
}
}
+ /** Returns whether this is a subprocess (e.g. com.android.systemui:screenshot) */
+ private boolean isSubprocess() {
+ String processName = ActivityThread.currentProcessName();
+ return processName != null && processName.contains(":");
+ }
+
/**
* Makes sure that all the CoreStartables are running. If they are already running, this is a
* no-op. This is needed to conditionally start all the services, as we only need to have it in
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 0e2e2e6..b8019ab 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -22,6 +22,7 @@
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
import com.android.systemui.shared.education.GestureType
@@ -73,7 +74,7 @@
implLazy.get()
} else {
// No-op implementation when the flag is disabled.
- return NoOpCoreStartable
+ return NoOpContextualEducationInteractor
}
}
@@ -88,6 +89,18 @@
return NoOpKeyboardTouchpadEduStatsInteractor
}
}
+
+ @Provides
+ fun provideKeyboardTouchpadEduInteractor(
+ implLazy: Lazy<KeyboardTouchpadEduInteractor>
+ ): CoreStartable {
+ return if (Flags.keyboardTouchpadContextualEducation()) {
+ implLazy.get()
+ } else {
+ // No-op implementation when the flag is disabled.
+ return NoOpKeyboardTouchpadEduInteractor
+ }
+ }
}
private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
@@ -96,7 +109,11 @@
override fun updateShortcutTriggerTime(gestureType: GestureType) {}
}
- private object NoOpCoreStartable : CoreStartable {
+ private object NoOpContextualEducationInteractor : CoreStartable {
+ override fun start() {}
+ }
+
+ private object NoOpKeyboardTouchpadEduInteractor : CoreStartable {
override fun start() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index e2aa911..3036d97 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -19,12 +19,17 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.shared.education.GestureType
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
/**
@@ -36,16 +41,28 @@
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val selectedUserInteractor: SelectedUserInteractor,
private val repository: ContextualEducationRepository,
) : CoreStartable {
+ val backGestureModelFlow = readEduModelsOnSignalCountChanged(GestureType.BACK_GESTURE)
+
override fun start() {
backgroundScope.launch {
selectedUserInteractor.selectedUser.collectLatest { repository.setUser(it) }
}
}
+ private fun readEduModelsOnSignalCountChanged(gestureType: GestureType): Flow<GestureEduModel> {
+ return repository
+ .readGestureEduModelFlow(gestureType)
+ .distinctUntilChanged(
+ areEquivalent = { old, new -> old.signalCount == new.signalCount }
+ )
+ .flowOn(backgroundDispatcher)
+ }
+
suspend fun incrementSignalCount(gestureType: GestureType) =
repository.incrementSignalCount(gestureType)
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
new file mode 100644
index 0000000..247abf1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.shared.model.EducationInfo
+import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
+
+/** Allow listening to new contextual education triggered */
+@SysUISingleton
+class KeyboardTouchpadEduInteractor
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val contextualEducationInteractor: ContextualEducationInteractor
+) : CoreStartable {
+
+ companion object {
+ const val MAX_SIGNAL_COUNT: Int = 2
+ }
+
+ private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
+ val educationTriggered = _educationTriggered.asStateFlow()
+
+ override fun start() {
+ backgroundScope.launch {
+ contextualEducationInteractor.backGestureModelFlow
+ .mapNotNull { getEduType(it) }
+ .collect { _educationTriggered.value = EducationInfo(BACK_GESTURE, it) }
+ }
+ }
+
+ private fun getEduType(model: GestureEduModel): EducationUiType? {
+ if (isEducationNeeded(model)) {
+ return EducationUiType.Toast
+ } else {
+ return null
+ }
+ }
+
+ private fun isEducationNeeded(model: GestureEduModel): Boolean {
+ // Todo: b/354884305 - add complete education logic to show education in correct scenarios
+ val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+ val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
+
+ return shortcutWasTriggered && signalCountReached
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
new file mode 100644
index 0000000..85f4012
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.shared.model
+
+import com.android.systemui.shared.education.GestureType
+
+/**
+ * Model for education triggered. [gestureType] indicates what gesture it is trying to educate about
+ * and [educationUiType] is how we educate user in the UI
+ */
+data class EducationInfo(val gestureType: GestureType, val educationUiType: EducationUiType)
+
+enum class EducationUiType {
+ Toast,
+ Notification,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 69a157f..addb014 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
+import static com.android.systemui.Flags.communalHub;
import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
@@ -90,6 +91,8 @@
import com.android.systemui.animation.GhostedViewTransitionAnimatorController;
import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -202,10 +205,12 @@
);
// Time in millis for playing turbulence noise that is played after a touch ripple.
- @VisibleForTesting static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
+ @VisibleForTesting
+ static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
private final SeekBarViewModel mSeekBarViewModel;
private final MediaFlags mMediaFlags;
+ private final CommunalSceneInteractor mCommunalSceneInteractor;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
private final DelayableExecutor mMainExecutor;
@@ -293,8 +298,9 @@
* Initialize a new control panel
*
* @param backgroundExecutor background executor, used for processing artwork
- * @param mainExecutor main thread executor, used if we receive callbacks on the background
- * thread that then trigger UI changes.
+ * @param mainExecutor main thread executor, used if we receive callbacks on the
+ * background
+ * thread that then trigger UI changes.
* @param activityStarter activity starter
*/
@Inject
@@ -314,6 +320,7 @@
MediaUiEventLogger logger,
KeyguardStateController keyguardStateController,
ActivityIntentHelper activityIntentHelper,
+ CommunalSceneInteractor communalSceneInteractor,
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController,
GlobalSettings globalSettings,
@@ -337,6 +344,7 @@
mLockscreenUserManager = lockscreenUserManager;
mBroadcastDialogController = broadcastDialogController;
mMediaFlags = mediaFlags;
+ mCommunalSceneInteractor = communalSceneInteractor;
mSeekBarViewModel.setLogSeek(() -> {
if (mPackageName != null && mInstanceId != null) {
@@ -375,6 +383,7 @@
/**
* Get the recommendation view holder used to display Smartspace media recs.
+ *
* @return the recommendation view holder
*/
@Nullable
@@ -693,7 +702,7 @@
// TODO(b/233698402): Use the package name instead of app label to avoid the
// unexpected result.
mIsCurrentBroadcastedApp = device != null
- && TextUtils.equals(device.getName(),
+ && TextUtils.equals(device.getName(),
mContext.getString(R.string.broadcasting_description_is_broadcasting));
useDisabledAlpha = !mIsCurrentBroadcastedApp;
// Always be enabled if the broadcast button is shown
@@ -764,7 +773,7 @@
PendingIntent deviceIntent = device.getIntent();
boolean showOverLockscreen = mKeyguardStateController.isShowing()
&& mActivityIntentHelper.wouldPendingShowOverLockscreen(
- deviceIntent, mLockscreenUserManager.getCurrentUserId());
+ deviceIntent, mLockscreenUserManager.getCurrentUserId());
if (deviceIntent.isActivity()) {
if (!showOverLockscreen) {
mActivityStarter.postStartActivityDismissingKeyguard(
@@ -825,24 +834,26 @@
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
return mMetadataAnimationHandler.setNext(
- new Triple(data.getSong(), data.getArtist(), data.isExplicit()),
- () -> {
- titleText.setText(data.getSong());
- artistText.setText(data.getArtist());
- setVisibleAndAlpha(expandedSet, R.id.media_explicit_indicator, data.isExplicit());
- setVisibleAndAlpha(collapsedSet, R.id.media_explicit_indicator, data.isExplicit());
+ new Triple(data.getSong(), data.getArtist(), data.isExplicit()),
+ () -> {
+ titleText.setText(data.getSong());
+ artistText.setText(data.getArtist());
+ setVisibleAndAlpha(expandedSet, R.id.media_explicit_indicator,
+ data.isExplicit());
+ setVisibleAndAlpha(collapsedSet, R.id.media_explicit_indicator,
+ data.isExplicit());
- // refreshState is required here to resize the text views (and prevent ellipsis)
- mMediaViewController.refreshState();
- return Unit.INSTANCE;
- },
- () -> {
- // After finishing the enter animation, we refresh state. This could pop if
- // something is incorrectly bound, but needs to be run if other elements were
- // updated while the enter animation was running
- mMediaViewController.refreshState();
- return Unit.INSTANCE;
- });
+ // refreshState is required here to resize the text views (and prevent ellipsis)
+ mMediaViewController.refreshState();
+ return Unit.INSTANCE;
+ },
+ () -> {
+ // After finishing the enter animation, we refresh state. This could pop if
+ // something is incorrectly bound, but needs to be run if other elements were
+ // updated while the enter animation was running
+ mMediaViewController.refreshState();
+ return Unit.INSTANCE;
+ });
}
// We may want to look into unifying this with bindRecommendationContentDescription if/when we
@@ -1105,7 +1116,7 @@
private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient,
ColorScheme mutableColorScheme, float startAlpha, float endAlpha) {
- gradient.setColors(new int[] {
+ gradient.setColors(new int[]{
ColorUtilKt.getColorWithAlpha(
MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
startAlpha),
@@ -1113,7 +1124,7 @@
MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
endAlpha),
});
- return new LayerDrawable(new Drawable[] { albumArt, gradient });
+ return new LayerDrawable(new Drawable[]{albumArt, gradient});
}
private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
@@ -1143,7 +1154,7 @@
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
if (semanticActions != null) {
// Hide all the generic buttons
- for (ImageButton b: genericButtons) {
+ for (ImageButton b : genericButtons) {
setVisibleAndAlpha(collapsedSet, b.getId(), false);
setVisibleAndAlpha(expandedSet, b.getId(), false);
}
@@ -1346,6 +1357,7 @@
/* shouldInverseNoiseLuminosity= */ false
);
}
+
private void clearButton(final ImageButton button) {
button.setImageDrawable(null);
button.setContentDescription(null);
@@ -1421,19 +1433,33 @@
// TODO(b/174236650): Make sure that the carousel indicator also fades out.
// TODO(b/174236650): Instrument the animation to measure jank.
- return new GhostedViewTransitionAnimatorController(player,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) {
- @Override
- protected float getCurrentTopCornerRadius() {
- return mContext.getResources().getDimension(R.dimen.notification_corner_radius);
- }
+ final ActivityTransitionAnimator.Controller controller =
+ new GhostedViewTransitionAnimatorController(player,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) {
+ @Override
+ protected float getCurrentTopCornerRadius() {
+ return mContext.getResources().getDimension(
+ R.dimen.notification_corner_radius);
+ }
- @Override
- protected float getCurrentBottomCornerRadius() {
- // TODO(b/184121838): Make IlluminationDrawable support top and bottom radius.
- return getCurrentTopCornerRadius();
- }
- };
+ @Override
+ protected float getCurrentBottomCornerRadius() {
+ // TODO(b/184121838): Make IlluminationDrawable support top and bottom
+ // radius.
+ return getCurrentTopCornerRadius();
+ }
+ };
+
+ // When on the hub, wrap in the communal animation controller to ensure we exit the hub
+ // at the proper stage of the animation.
+ if (communalHub()
+ && mMediaViewController.getCurrentEndLocation()
+ == MediaHierarchyManager.LOCATION_COMMUNAL_HUB) {
+ mCommunalSceneInteractor.setIsLaunchingWidget(true);
+ return new CommunalTransitionAnimatorController(controller,
+ mCommunalSceneInteractor);
+ }
+ return controller;
}
/** Bind this recommendation view based on the given data. */
@@ -1934,6 +1960,7 @@
/**
* Get the surface given the current end location for MediaViewController
+ *
* @return surface used for Smartspace logging
*/
protected int getSurfaceForSmartspaceLogging() {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
new file mode 100644
index 0000000..56270ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.annotation.SuppressLint
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.widget.Switch
+import android.widget.TextView
+import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.traceur.PresetTraceConfigs
+import com.android.traceur.TraceConfig
+import com.android.traceur.res.R as T
+import java.util.function.Consumer
+
+class CustomTraceSettingsDialogDelegate(
+ private val factory: SystemUIDialog.Factory,
+ private val customTraceState: CustomTraceState,
+ private val tagTitles: Set<String>,
+ private val onSave: Runnable,
+) : SystemUIDialog.Delegate {
+
+ private val builder = TraceConfig.Builder(customTraceState.traceConfig)
+
+ override fun createDialog(): SystemUIDialog = factory.create(this)
+
+ override fun beforeCreate(dialog: SystemUIDialog?, savedInstanceState: Bundle?) {
+ super.beforeCreate(dialog, savedInstanceState)
+
+ dialog?.apply {
+ setTitle(R.string.custom_trace_settings_dialog_title)
+ setView(
+ LayoutInflater.from(context).inflate(R.layout.custom_trace_settings_dialog, null)
+ )
+ setPositiveButton(R.string.save) { _, _ ->
+ onSave.run()
+ customTraceState.traceConfig = builder.build()
+ }
+ setNegativeButton(R.string.cancel) { _, _ -> }
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ override fun onCreate(dialog: SystemUIDialog?, savedInstanceState: Bundle?) {
+ super.onCreate(dialog, savedInstanceState)
+
+ dialog?.apply {
+ requireViewById<TextView>(R.id.categories).apply {
+ text =
+ context.getString(T.string.categories) +
+ "\n" +
+ if (
+ builder.tags == null ||
+ builder.tags!! == PresetTraceConfigs.getDefaultConfig().tags
+ ) {
+ context.getString(R.string.notification_alert_title)
+ } else {
+ tagTitles
+ .filter {
+ builder.tags!!.contains(it.substringBefore(TAG_TITLE_DELIMITER))
+ }
+ .joinToString()
+ }
+ setOnClickListener { showCategorySelector(this) }
+ }
+ requireViewById<Switch>(R.id.attach_to_bugreport_switch).apply {
+ isChecked = builder.attachToBugreport
+ setOnCheckedChangeListener { _, isChecked -> builder.attachToBugreport = isChecked }
+ }
+ requireViewById<TextView>(R.id.cpu_buffer_size).setupSingleChoiceText(
+ T.array.buffer_size_values,
+ T.array.buffer_size_names,
+ builder.bufferSizeKb,
+ T.string.buffer_size,
+ ) {
+ builder.bufferSizeKb = it
+ }
+ val longTraceSizeText: TextView =
+ requireViewById<TextView>(R.id.long_trace_size).setupSingleChoiceText(
+ T.array.long_trace_size_values,
+ T.array.long_trace_size_names,
+ builder.maxLongTraceSizeMb,
+ T.string.max_long_trace_size,
+ ) {
+ builder.maxLongTraceSizeMb = it
+ }
+ val longTraceDurationText: TextView =
+ requireViewById<TextView>(R.id.long_trace_duration).setupSingleChoiceText(
+ T.array.long_trace_duration_values,
+ T.array.long_trace_duration_names,
+ builder.maxLongTraceDurationMinutes,
+ T.string.max_long_trace_duration,
+ ) {
+ builder.maxLongTraceDurationMinutes = it
+ }
+ requireViewById<Switch>(R.id.long_traces_switch).apply {
+ isChecked = builder.longTrace
+ val disabledAlpha by lazy { getDisabledAlpha(context) }
+ val alpha = if (isChecked) 1f else disabledAlpha
+ longTraceDurationText.alpha = alpha
+ longTraceSizeText.alpha = alpha
+
+ setOnCheckedChangeListener { _, isChecked ->
+ builder.longTrace = isChecked
+ longTraceDurationText.isEnabled = isChecked
+ longTraceSizeText.isEnabled = isChecked
+
+ val newAlpha = if (isChecked) 1f else disabledAlpha
+ longTraceDurationText.alpha = newAlpha
+ longTraceSizeText.alpha = newAlpha
+ }
+ }
+ requireViewById<Switch>(R.id.winscope_switch).apply {
+ isChecked = builder.winscope
+ setOnCheckedChangeListener { _, isChecked -> builder.winscope = isChecked }
+ }
+ requireViewById<Switch>(R.id.trace_debuggable_apps_switch).apply {
+ isChecked = builder.apps
+ setOnCheckedChangeListener { _, isChecked -> builder.apps = isChecked }
+ }
+ requireViewById<TextView>(R.id.long_traces_switch_label).text =
+ context.getString(T.string.long_traces)
+ requireViewById<TextView>(R.id.debuggable_apps_switch_label).text =
+ context.getString(T.string.trace_debuggable_applications)
+ requireViewById<TextView>(R.id.winscope_switch_label).text =
+ context.getString(T.string.winscope_tracing)
+ requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text =
+ context.getString(T.string.attach_to_bug_report)
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private fun showCategorySelector(root: TextView) {
+ showDialog(root.context) {
+ val tags = builder.tags ?: PresetTraceConfigs.getDefaultConfig().tags
+ val titlesToCheckmarks =
+ tagTitles.associateBy(
+ { it },
+ { tags.contains(it.substringBefore(TAG_TITLE_DELIMITER)) }
+ )
+ val titles = titlesToCheckmarks.keys.toTypedArray()
+ val checkmarks = titlesToCheckmarks.values.toBooleanArray()
+ val checkedTitleSuffixes =
+ titlesToCheckmarks.entries
+ .filter { it.value }
+ .map { it.key.substringAfter(TAG_TITLE_DELIMITER) }
+ .toMutableSet()
+
+ val newTags = tags.toMutableSet()
+ setMultiChoiceItems(titles, checkmarks) { _, i, isChecked ->
+ val tag = titles[i].substringBefore(TAG_TITLE_DELIMITER)
+ val titleSuffix = titles[i].substringAfter(TAG_TITLE_DELIMITER)
+ if (isChecked) {
+ newTags.add(tag)
+ checkedTitleSuffixes.add(titleSuffix)
+ } else {
+ newTags.remove(tag)
+ checkedTitleSuffixes.remove(titleSuffix)
+ }
+ }
+ setPositiveButton(R.string.save) { _, _ ->
+ root.text =
+ root.context.resources.getString(T.string.categories) +
+ "\n" +
+ checkedTitleSuffixes.joinToString()
+ builder.tags = newTags
+ }
+ setNeutralButton(R.string.restore_default) { _, _ ->
+ root.text =
+ context.getString(T.string.categories) +
+ "\n" +
+ context.getString(R.string.notification_alert_title)
+ builder.tags = null
+ }
+ setNegativeButton(R.string.cancel) { _, _ -> }
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private fun TextView.setupSingleChoiceText(
+ resValues: Int,
+ resNames: Int,
+ startingValue: Int,
+ alertTitleRes: Int,
+ onChosen: Consumer<Int>,
+ ): TextView {
+ val values = resources.getStringArray(resValues).map { Integer.parseInt(it) }
+ val names = resources.getStringArray(resNames)
+ val startingIndex = values.indexOf(startingValue)
+ text = resources.getString(alertTitleRes) + "\n${names[startingIndex]}"
+
+ setOnClickListener {
+ showDialog(context) {
+ setTitle(alertTitleRes)
+ setSingleChoiceItems(names, startingIndex) { d, i ->
+ text = resources.getString(alertTitleRes) + "\n${names[i]}"
+ onChosen.accept(values[i])
+ d.dismiss()
+ }
+ }
+ }
+ return this
+ }
+
+ private fun showDialog(context: Context, onBuilder: AlertDialog.Builder.() -> Unit) =
+ AlertDialog.Builder(context, R.style.Theme_SystemUI_Dialog_Alert)
+ .apply { onBuilder() }
+ .create()
+ .also { SystemUIDialog.applyFlags(it) }
+ .show()
+
+ private fun getDisabledAlpha(context: Context): Float {
+ val ta = context.obtainStyledAttributes(intArrayOf(android.R.attr.disabledAlpha))
+ val alpha = ta.getFloat(0, 0f)
+ ta.recycle()
+ return alpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
new file mode 100644
index 0000000..14dfcc5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.SharedPreferences
+import com.android.traceur.PresetTraceConfigs.TraceOptions
+import com.android.traceur.PresetTraceConfigs.getDefaultConfig
+import com.android.traceur.TraceConfig
+
+class CustomTraceState(private val prefs: SharedPreferences) {
+
+ private var enabledTags: Set<String>?
+ get() = prefs.getStringSet(KEY_TAGS, getDefaultConfig().tags) ?: getDefaultConfig().tags
+ set(value) = prefs.edit().putStringSet(KEY_TAGS, value).apply()
+
+ var traceConfig: TraceConfig
+ get() = TraceConfig(options, enabledTags)
+ set(value) {
+ enabledTags = value.tags
+ options = value.options
+ }
+
+ private var options: TraceOptions
+ get() =
+ TraceOptions(
+ prefs.getInt(KEY_CUSTOM_BUFFER_SIZE_KB, getDefaultConfig().bufferSizeKb),
+ prefs.getBoolean(KEY_WINSCOPE, getDefaultConfig().winscope),
+ prefs.getBoolean(KEY_APPS, getDefaultConfig().apps),
+ prefs.getBoolean(KEY_LONG_TRACE, getDefaultConfig().longTrace),
+ prefs.getBoolean(KEY_ATTACH_TO_BUGREPORT, getDefaultConfig().attachToBugreport),
+ prefs.getInt(KEY_LONG_TRACE_SIZE_MB, getDefaultConfig().maxLongTraceSizeMb),
+ prefs.getInt(
+ KEY_LONG_TRACE_DURATION_MINUTES,
+ getDefaultConfig().maxLongTraceDurationMinutes
+ ),
+ )
+ set(value) {
+ prefs
+ .edit()
+ .putInt(KEY_CUSTOM_BUFFER_SIZE_KB, value.bufferSizeKb)
+ .putBoolean(KEY_WINSCOPE, value.winscope)
+ .putBoolean(KEY_APPS, value.apps)
+ .putBoolean(KEY_LONG_TRACE, value.longTrace)
+ .putBoolean(KEY_ATTACH_TO_BUGREPORT, value.attachToBugreport)
+ .putInt(KEY_LONG_TRACE_SIZE_MB, value.maxLongTraceSizeMb)
+ .putInt(KEY_LONG_TRACE_DURATION_MINUTES, value.maxLongTraceDurationMinutes)
+ .apply()
+ }
+
+ companion object {
+ private const val KEY_CUSTOM_BUFFER_SIZE_KB = "key_bufferSizeKb"
+ private const val KEY_WINSCOPE = "key_winscope"
+ private const val KEY_APPS = "key_apps"
+ private const val KEY_LONG_TRACE = "key_longTrace"
+ private const val KEY_ATTACH_TO_BUGREPORT = "key_attachToBugReport"
+ private const val KEY_LONG_TRACE_SIZE_MB = "key_maxLongTraceSizeMb"
+ private const val KEY_LONG_TRACE_DURATION_MINUTES = "key_maxLongTraceDurationInMinutes"
+ private const val KEY_TAGS = "key_tags"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 7612900..16642ab 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -38,6 +38,8 @@
private val prefs =
userFileManager.getSharedPreferences(TILE_SPEC, Context.MODE_PRIVATE, userTracker.userId)
+ val customTraceState = CustomTraceState(prefs)
+
var takeBugreport
get() = prefs.getBoolean(KEY_TAKE_BUG_REPORT, false)
set(value) = prefs.edit().putBoolean(KEY_TAKE_BUG_REPORT, value).apply()
@@ -55,7 +57,12 @@
set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_RES, value).apply()
val traceConfig: TraceConfig
- get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceConfigs.getDefaultConfig()
+ get() = ALL_ISSUE_TYPES[issueTypeRes] ?: customTraceState.traceConfig
+
+ // The 1st part of the title before the ": " is the tag, and the 2nd part is the description
+ var tagTitles: Set<String>
+ get() = prefs.getStringSet(KEY_TAG_TITLES, emptySet()) ?: emptySet()
+ set(value) = prefs.edit().putStringSet(KEY_TAG_TITLES, value).apply()
private val listeners = CopyOnWriteArrayList<Runnable>()
@@ -81,8 +88,10 @@
private const val KEY_TAKE_BUG_REPORT = "key_takeBugReport"
private const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
private const val KEY_RECORD_SCREEN = "key_recordScreen"
+ private const val KEY_TAG_TITLES = "key_tagTitles"
const val KEY_ISSUE_TYPE_RES = "key_issueTypeRes"
const val ISSUE_TYPE_NOT_SET = -1
+ const val TAG_TITLE_DELIMITER = ": "
val ALL_ISSUE_TYPES: Map<Int, TraceConfig?> =
hashMapOf(
@@ -90,6 +99,7 @@
Pair(R.string.user_interface, PresetTraceConfigs.getUiConfig()),
Pair(R.string.battery, PresetTraceConfigs.getBatteryConfig()),
Pair(R.string.thermal, PresetTraceConfigs.getThermalConfig()),
+ Pair(R.string.custom, null),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 8a51ad4..dd2dbf3 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -88,7 +88,7 @@
setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
}
bgExecutor.execute {
- traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags() }
+ traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags(state) }
traceurMessageSender.bindToTraceur(dialog.context)
}
}
@@ -170,18 +170,8 @@
@MainThread
private fun onIssueTypeClicked(context: Context, onIssueTypeSelected: Runnable) {
val popupMenu = PopupMenu(context, issueTypeButton)
-
- ALL_ISSUE_TYPES.keys.forEach {
- popupMenu.menu.add(it).apply {
- setIcon(R.drawable.arrow_pointing_down)
- if (it != state.issueTypeRes) {
- iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
- }
- intent = Intent().putExtra(KEY_ISSUE_TYPE_RES, it)
- }
- }
- popupMenu.apply {
- setOnMenuItemClickListener {
+ val onMenuItemClickListener =
+ PopupMenu.OnMenuItemClickListener {
issueTypeButton.text = it.title
state.issueTypeRes =
it.intent?.getIntExtra(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
@@ -189,6 +179,32 @@
onIssueTypeSelected.run()
true
}
+ ALL_ISSUE_TYPES.keys.forEach {
+ popupMenu.menu.add(it).apply {
+ setIcon(R.drawable.arrow_pointing_down)
+ if (it != state.issueTypeRes) {
+ iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+ }
+ intent = Intent().putExtra(KEY_ISSUE_TYPE_RES, it)
+
+ if (it == R.string.custom) {
+ setOnMenuItemClickListener {
+ CustomTraceSettingsDialogDelegate(
+ factory,
+ state.customTraceState,
+ state.tagTitles
+ ) {
+ onMenuItemClickListener.onMenuItemClick(it)
+ }
+ .createDialog()
+ .show()
+ true
+ }
+ }
+ }
+ }
+ popupMenu.apply {
+ setOnMenuItemClickListener(onMenuItemClickListener)
setForceShowIcon(true)
show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
index a31a9ef..8bfd14a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
@@ -33,6 +33,7 @@
import androidx.annotation.WorkerThread
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER
import com.android.traceur.FileSender
import com.android.traceur.MessageConstants
import com.android.traceur.TraceConfig
@@ -112,8 +113,8 @@
}
@WorkerThread
- fun getTags() {
- val replyHandler = Messenger(TagsHandler(backgroundLooper))
+ fun getTags(state: IssueRecordingState) {
+ val replyHandler = Messenger(TagsHandler(backgroundLooper, state))
notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler)
}
@@ -165,7 +166,8 @@
}
}
- private class TagsHandler(looper: Looper) : Handler(looper) {
+ private class TagsHandler(looper: Looper, private val state: IssueRecordingState) :
+ Handler(looper) {
override fun handleMessage(msg: Message) {
if (MessageConstants.TAGS_WHAT == msg.what) {
@@ -174,16 +176,11 @@
msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS)
if (keys == null || values == null) {
throw IllegalArgumentException(
- "Neither keys: $keys, nor values: $values can " + "be null"
+ "Neither keys: $keys, nor values: $values can be null"
)
}
-
- val tags = keys.zip(values).map { "${it.first}: ${it.second}" }.toSet()
- Log.e(
- TAG,
- "These tags: $tags will be saved and used for the Custom Trace" +
- " Config dialog in a future CL. This log will be removed."
- )
+ state.tagTitles =
+ keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet()
} else {
throw IllegalArgumentException("received unknown msg.what: " + msg.what)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index bd08685..67032f7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -789,6 +789,9 @@
/** update Qs height state */
void setExpansionHeight(float height) {
+ if (mExpansionHeight == height) {
+ return;
+ }
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 5b25b11..0103fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -28,6 +28,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.expansionChanges
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -87,6 +89,7 @@
private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
+ private val sceneInteractor: SceneInteractor,
) : Coordinator, Dumpable {
private val unseenNotifications = mutableSetOf<NotificationEntry>()
@@ -106,12 +109,15 @@
// Whether or not keyguard is visible (or occluded).
@Suppress("DEPRECATION")
val isKeyguardPresentFlow: Flow<Boolean> =
- keyguardTransitionInteractor
- .transitionValue(
- scene = Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE,
- )
- .map { it == 0f }
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.map {
+ !it.isTransitioning(to = Scenes.Gone) && !it.isIdle(Scenes.Gone)
+ }
+ } else {
+ keyguardTransitionInteractor.transitions.map { step ->
+ step.to != KeyguardState.GONE
+ }
+ }
.distinctUntilChanged()
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index 99ed2d9..cd442cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -21,6 +21,7 @@
import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
@@ -96,7 +97,8 @@
fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
- val spn = getStringExtra(EXTRA_DATA_SPN)
+ val spn = getStringExtra(EXTRA_SPN)
+ val dataSpn = getStringExtra(EXTRA_DATA_SPN)
val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false)
val plmn = getStringExtra(EXTRA_PLMN)
@@ -112,6 +114,12 @@
}
str.append(spn)
}
+ if (showSpn && dataSpn != null) {
+ if (str.isNotEmpty()) {
+ str.append(separator)
+ }
+ str.append(dataSpn)
+ }
return if (str.isNotEmpty()) NetworkNameModel.IntentDerived(str.toString()) else null
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index fbfe41f..521aa5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -69,6 +69,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -211,6 +212,8 @@
@Mock private lateinit var activityIntentHelper: ActivityIntentHelper
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+ @Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor
+
@Mock private lateinit var recommendationViewHolder: RecommendationViewHolder
@Mock private lateinit var smartspaceAction: SmartspaceAction
private lateinit var smartspaceData: SmartspaceMediaData
@@ -271,6 +274,7 @@
logger,
keyguardStateController,
activityIntentHelper,
+ communalSceneInteractor,
lockscreenUserManager,
broadcastDialogController,
globalSettings,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 9a6423d..7ab3e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -61,7 +61,7 @@
public void testCloseQsSideEffects() {
enableSplitShade(true);
mQsController.setExpandImmediate(true);
- mQsController.setExpanded(true);
+ mQsController.setExpansionHeight(800);
mQsController.closeQs();
assertThat(mQsController.getExpanded()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 8fd0b31..9d83d5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -59,6 +59,7 @@
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
@@ -85,7 +86,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
@@ -93,8 +93,6 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
@@ -112,6 +110,8 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@@ -815,9 +815,11 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.value!!.onReceive(context, intent)
+ captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+ // spnIntent() sets all values to true and test strings
+ assertThat(latest)
+ .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
job.cancel()
}
@@ -831,17 +833,19 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.value!!.onReceive(context, intent)
+ captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+ assertThat(latest)
+ .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
// WHEN an intent with a different subId is sent
val wrongSubIntent = spnIntent(subId = 101)
- captor.value!!.onReceive(context, wrongSubIntent)
+ captor.lastValue.onReceive(context, wrongSubIntent)
// THEN the previous intent's name is still used
- assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+ assertThat(latest)
+ .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
job.cancel()
}
@@ -855,9 +859,10 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.value!!.onReceive(context, intent)
+ captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+ assertThat(latest)
+ .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
val intentWithoutInfo =
spnIntent(
@@ -865,7 +870,7 @@
showPlmn = false,
)
- captor.value!!.onReceive(context, intentWithoutInfo)
+ captor.lastValue.onReceive(context, intentWithoutInfo)
assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
@@ -884,10 +889,88 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.value!!.onReceive(context, intent)
+ captor.lastValue.onReceive(context, intent)
// The value is still there despite no active subscribers
- assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP))
+ assertThat(underTest.networkName.value)
+ .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ fun networkName_allFieldsSet() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.networkName)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+ }
+
+ @Test
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.networkName)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ fun networkName_showPlmn_noShowSPN() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.networkName)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = false,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+ }
+
+ @Test
+ fun networkName_showPlmn_plmnNull_showSpn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.networkName)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN$SEP$DATA_SPN"))
}
@Test
@@ -1128,14 +1211,16 @@
private fun spnIntent(
subId: Int = SUB_1_ID,
showSpn: Boolean = true,
- spn: String = SPN,
+ spn: String? = SPN,
+ dataSpn: String? = DATA_SPN,
showPlmn: Boolean = true,
- plmn: String = PLMN,
+ plmn: String? = PLMN,
): Intent =
Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
putExtra(EXTRA_SHOW_SPN, showSpn)
putExtra(EXTRA_SPN, spn)
+ putExtra(EXTRA_DATA_SPN, dataSpn)
putExtra(EXTRA_SHOW_PLMN, showPlmn)
putExtra(EXTRA_PLMN, plmn)
}
@@ -1148,6 +1233,7 @@
private const val SEP = "-"
private const val SPN = "testSpn"
+ private const val DATA_SPN = "testDataSpn"
private const val PLMN = "testPlmn"
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index 5410882..bade91a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -28,11 +28,15 @@
private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
private val _gestureEduModels = MutableStateFlow(GestureEduModel())
private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+ private var currentUser: Int = 0
override fun setUser(userId: Int) {
if (!userGestureMap.contains(userId)) {
userGestureMap[userId] = GestureEduModel()
}
+ // save data of current user to the map
+ userGestureMap[currentUser] = _gestureEduModels.value
+ // switch to data of new user
_gestureEduModels.value = userGestureMap[userId]!!
}
@@ -41,13 +45,15 @@
}
override suspend fun incrementSignalCount(gestureType: GestureType) {
+ val originalModel = _gestureEduModels.value
_gestureEduModels.value =
- GestureEduModel(
+ originalModel.copy(
signalCount = _gestureEduModels.value.signalCount + 1,
)
}
override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
- _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+ val originalModel = _gestureEduModels.value
+ _gestureEduModels.value = originalModel.copy(lastShortcutTriggeredTime = clock.instant())
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
index 5b2dc2b..a7b322b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -25,6 +26,7 @@
Kosmos.Fixture {
ContextualEducationInteractor(
backgroundScope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
repository = contextualEducationRepository,
selectedUserInteractor = selectedUserInteractor
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 8f84e04..fb4e901 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -19,6 +19,14 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+var Kosmos.keyboardTouchpadEduInteractor by
+ Kosmos.Fixture {
+ KeyboardTouchpadEduInteractor(
+ backgroundScope = testScope.backgroundScope,
+ contextualEducationInteractor = contextualEducationInteractor
+ )
+ }
+
var Kosmos.keyboardTouchpadEduStatsInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduStatsInteractorImpl(
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 311addb..efa1397 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -26,6 +26,7 @@
},
srcs: [
":services.accessibility-sources",
+ ":statslog-accessibility-java-gen",
"//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
],
libs: [
@@ -37,7 +38,6 @@
"a11ychecker-protos-java-proto-lite",
"com_android_server_accessibility_flags_lib",
"//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
-
],
}
@@ -81,3 +81,12 @@
"java/**/a11ychecker/proto/*.proto",
],
}
+
+genrule {
+ name: "statslog-accessibility-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module accessibility" +
+ " --javaPackage com.android.server.accessibility.a11ychecker" +
+ " --javaClass AccessibilityCheckerStatsLog --minApiLevel 34",
+ out: ["java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsLog.java"],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
new file mode 100644
index 0000000..1b3ec5a
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 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.accessibility.a11ychecker;
+
+import android.util.Slog;
+
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
+
+import java.util.Set;
+
+
+/**
+ * Wraps the StatsdLogger for AccessibilityCheckResultReported.
+ *
+ * @hide
+ */
+public class AccessibilityCheckerStatsdLogger {
+ private static final int ATOM_ID = 910;
+ private static final String LOG_TAG = "AccessibilityCheckerStatsdLogger";
+
+ /**
+ * Writes results to statsd.
+ */
+ public static void logResults(Set<AccessibilityCheckResultReported> results) {
+ Slog.i(LOG_TAG, String.format("Writing %d AccessibilityCheckResultReported events",
+ results.size()));
+
+ for (AccessibilityCheckResultReported result : results) {
+ AccessibilityCheckerStatsLog.write(ATOM_ID,
+ result.getPackageName(),
+ result.getAppVersionCode(),
+ result.getUiElementPath(),
+ result.getActivityName(),
+ result.getWindowTitle(),
+ result.getSourceComponentName(),
+ result.getSourceVersionCode(),
+ result.getResultCheckClass().getNumber(),
+ result.getResultType().getNumber(),
+ result.getResultId());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index d42a3dc..0815384 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -155,6 +155,15 @@
private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
+ // Comma separated list of all packages exempt from user impact level threshold. If a package
+ // in the list is crash looping, all the mitigations including factory reset will be performed.
+ private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+ "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
+
+ // Comma separated list of default packages exempt from user impact level threshold.
+ private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
+ "com.android.systemui";
+
private long mNumberOfNativeCrashPollsRemaining;
private static final int DB_VERSION = 1;
@@ -201,6 +210,8 @@
private final DeviceConfig.OnPropertiesChangedListener
mOnPropertyChangedListener = this::onPropertyChanged;
+ private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
+
// The set of packages that have been synced with the ExplicitHealthCheckController
@GuardedBy("mLock")
private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
@@ -523,7 +534,7 @@
@FailureReasons int failureReason,
int currentObserverImpact,
int mitigationCount) {
- if (currentObserverImpact < getUserImpactLevelLimit()) {
+ if (allowMitigations(currentObserverImpact, versionedPackage)) {
synchronized (mLock) {
mLastMitigation = mSystemClock.uptimeMillis();
}
@@ -531,6 +542,13 @@
}
}
+ private boolean allowMitigations(int currentObserverImpact,
+ VersionedPackage versionedPackage) {
+ return currentObserverImpact < getUserImpactLevelLimit()
+ || getPackagesExemptFromImpactLevelThreshold().contains(
+ versionedPackage.getPackageName());
+ }
+
private long getMitigationWindowMs() {
return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
}
@@ -662,6 +680,15 @@
DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
}
+ private Set<String> getPackagesExemptFromImpactLevelThreshold() {
+ if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
+ String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
+ DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
+ return Set.of(packageNames.split("\\s*,\\s*"));
+ }
+ return mPackagesExemptFromImpactLevelThreshold;
+ }
+
/** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 504c54a..ab63e24 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2864,6 +2864,11 @@
}
}
+ if (newAdj == clientAdj && app.isolated) {
+ // Make bound isolated processes have slightly worse score than their client
+ newAdj = clientAdj + 1;
+ }
+
if (adj > newAdj) {
adj = newAdj;
if (state.setCurRawAdj(adj, dryRun)) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3d41f05..7a24e9df 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -496,19 +496,60 @@
private AudioSystemThread mAudioSystemThread;
/** @see AudioHandler */
private AudioHandler mAudioHandler;
- /** @see VolumeStreamState */
- private VolumeStreamState[] mStreamStates;
+ /**
+ * @see VolumeStreamState
+ * Mapping which contains for each stream type its associated {@link VolumeStreamState}
+ **/
+ private SparseArray<VolumeStreamState> mStreamStates;
/*package*/ int getVssVolumeForDevice(int stream, int device) {
- return mStreamStates[stream].getIndex(device);
+ final VolumeStreamState streamState = mStreamStates.get(stream);
+ return streamState != null ? streamState.getIndex(device) : -1;
}
- /*package*/ VolumeStreamState getVssVolumeForStream(int stream) {
- return mStreamStates[stream];
+ /**
+ * Returns the {@link VolumeStreamState} corresponding to the passed stream type. This can be
+ * {@code null} since not all possible stream types have a valid {@link VolumeStreamState} (e.g.
+ * {@link AudioSystem#STREAM_BLUETOOTH_SCO}) is deprecated and will return a {@code null} stream
+ * state).
+ *
+ * @param stream the stream type for querying the stream state
+ *
+ * @return the {@link VolumeStreamState} corresponding to the passed stream type or {@code null}
+ */
+ @Nullable
+ /*package*/ VolumeStreamState getVssForStream(int stream) {
+ return mStreamStates.get(stream);
+ }
+
+ /**
+ * Returns the {@link VolumeStreamState} corresponding to the passed stream type. In case
+ * there is no associated stream state for the given stream type we return the default stream
+ * state for {@link AudioSystem#STREAM_MUSIC} (or throw an {@link IllegalArgumentException} in
+ * the ramp up phase of the replaceStreamBtSco flag to ensure that this case will never happen).
+ *
+ * @param stream the stream type for querying the stream state
+ *
+ * @return the {@link VolumeStreamState} corresponding to the passed stream type
+ */
+ @NonNull
+ /*package*/ VolumeStreamState getVssForStreamOrDefault(int stream) {
+ VolumeStreamState streamState = mStreamStates.get(stream);
+ if (streamState == null) {
+ if (replaceStreamBtSco()) {
+ throw new IllegalArgumentException("No VolumeStreamState for stream " + stream);
+ } else {
+ Log.e(TAG, "No VolumeStreamState for stream " + stream
+ + ". Returning default state for STREAM_MUSIC", new Exception());
+ streamState = mStreamStates.get(AudioSystem.STREAM_MUSIC);
+ }
+ }
+ return streamState;
}
/*package*/ int getMaxVssVolumeForStream(int stream) {
- return mStreamStates[stream].getMaxIndex();
+ final VolumeStreamState streamState = mStreamStates.get(stream);
+ return streamState != null ? streamState.getMaxIndex() : -1;
}
private SettingsObserver mSettingsObserver;
@@ -550,13 +591,13 @@
0 // STREAM_ASSISTANT
};
- /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
+ /* sStreamVolumeAlias[] indicates for each stream if it uses the volume settings
* of another stream: This avoids multiplying the volume settings for hidden
* stream types that follow other stream behavior for volume settings
* NOTE: do not create loops in aliases!
* Some streams alias to different streams according to device category (phone or tablet) or
* use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
- * mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
+ * sStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
* (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
* STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
@@ -621,12 +662,12 @@
AudioSystem.STREAM_MUSIC, // STREAM_ACCESSIBILITY
AudioSystem.STREAM_MUSIC // STREAM_ASSISTANT
};
- protected static int[] mStreamVolumeAlias;
+ protected static SparseIntArray sStreamVolumeAlias;
private static final int UNSET_INDEX = -1;
/**
* Map AudioSystem.STREAM_* constants to app ops. This should be used
- * after mapping through mStreamVolumeAlias.
+ * after mapping through sStreamVolumeAlias.
*/
private static final int[] STREAM_VOLUME_OPS = new int[] {
AppOpsManager.OP_AUDIO_VOICE_VOLUME, // STREAM_VOICE_CALL
@@ -1416,7 +1457,7 @@
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
- // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
+ // must be called before readPersistedSettings() which needs a valid sStreamVolumeAlias[]
// array initialized by updateStreamVolumeAlias()
updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
readPersistedSettings();
@@ -1473,7 +1514,7 @@
int numStreamTypes = AudioSystem.getNumStreamTypes();
synchronized (VolumeStreamState.class) {
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- VolumeStreamState streamState = mStreamStates[streamType];
+ final VolumeStreamState streamState = getVssForStream(streamType);
if (streamState == null) {
continue;
}
@@ -2083,7 +2124,10 @@
// keep track of any error during stream volume initialization
int status = AudioSystem.AUDIO_STATUS_OK;
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- VolumeStreamState streamState = mStreamStates[streamType];
+ VolumeStreamState streamState = getVssForStream(streamType);
+ if (streamState == null) {
+ continue;
+ }
final int res = AudioSystem.initStreamVolume(
streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -2243,12 +2287,13 @@
synchronized (VolumeStreamState.class) {
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
- if (mStreamVolumeAlias[streamType] >= 0) {
- mStreamStates[streamType]
- .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG);
+ int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ final VolumeStreamState streamState = getVssForStream(streamType);
+ if (streamAlias != -1 && streamState != null) {
+ streamState.setAllIndexes(getVssForStream(streamAlias), TAG);
// apply stream volume
- if (!mStreamStates[streamType].mIsMuted) {
- mStreamStates[streamType].applyAllVolumes();
+ if (!streamState.mIsMuted) {
+ streamState.applyAllVolumes();
}
}
}
@@ -2348,11 +2393,17 @@
if (device == AudioSystem.DEVICE_OUT_SPEAKER_SAFE) {
device = AudioSystem.DEVICE_OUT_SPEAKER;
}
- if (!mStreamStates[streamType].hasIndexForDevice(device)) {
+
+ final VolumeStreamState streamState = getVssForStream(streamType);
+ if (streamState == null) {
+ // nothing to update
+ return;
+ }
+
+ if (!streamState.hasIndexForDevice(device)) {
// set the default value, if device is affected by a full/fix/abs volume rule, it
// will taken into account in checkFixedVolumeDevices()
- mStreamStates[streamType].setIndex(
- mStreamStates[mStreamVolumeAlias[streamType]]
+ streamState.setIndex(getVssForStreamOrDefault(sStreamVolumeAlias.get(streamType))
.getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
device, caller, true /*hasModifyAudioSettings*/);
}
@@ -2365,11 +2416,11 @@
for (AudioDeviceAttributes deviceAttributes : devicesForAttributes) {
if (deviceAttributes.getType() == AudioDeviceInfo.convertInternalDeviceToDeviceType(
device)) {
- mStreamStates[streamType].checkFixedVolumeDevices();
+ streamState.checkFixedVolumeDevices();
// Unmute streams if required and device is full volume
if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) {
- mStreamStates[streamType].mute(false, "updateVolumeStates(" + caller);
+ streamState.mute(false, "updateVolumeStates(" + caller);
}
}
}
@@ -2379,22 +2430,27 @@
{
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
- if (mStreamStates[streamType] != null) {
- mStreamStates[streamType].checkFixedVolumeDevices();
+ final VolumeStreamState vss = getVssForStream(streamType);
+ if (vss != null) {
+ vss.checkFixedVolumeDevices();
}
}
}
private void checkAllFixedVolumeDevices(int streamType) {
- mStreamStates[streamType].checkFixedVolumeDevices();
+ final VolumeStreamState vss = getVssForStream(streamType);
+ if (vss == null) {
+ return;
+ }
+ vss.checkFixedVolumeDevices();
}
private void checkMuteAffectedStreams() {
// any stream with a min level > 0 is not muteable by definition
// STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications
// that has the the MODIFY_PHONE_STATE permission.
- for (int i = 0; i < mStreamStates.length; i++) {
- final VolumeStreamState vss = mStreamStates[i];
+ for (int i = 0; i < mStreamStates.size(); i++) {
+ final VolumeStreamState vss = mStreamStates.valueAt(i);
if (vss != null && vss.mIndexMin > 0
&& (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL
&& vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
@@ -2406,13 +2462,14 @@
private void createStreamStates() {
int numStreamTypes = AudioSystem.getNumStreamTypes();
- VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
+ mStreamStates = new SparseArray<>(numStreamTypes);
for (int i = 0; i < numStreamTypes; i++) {
- // a negative mStreamVolumeAlias value means the stream state type is not supported
- if (mStreamVolumeAlias[i] >= 0) {
- streams[i] =
- new VolumeStreamState(System.VOLUME_SETTINGS_INT[mStreamVolumeAlias[i]], i);
+ final int streamAlias = sStreamVolumeAlias.get(i, /*valueIfKeyNotFound=*/-1);
+ // a negative sStreamVolumeAlias value means the stream state type is not supported
+ if (streamAlias >= 0) {
+ mStreamStates.set(i,
+ new VolumeStreamState(System.VOLUME_SETTINGS_INT[streamAlias], i));
}
}
@@ -2431,24 +2488,25 @@
* For other volume groups not linked to any streams, default music stream index is considered.
*/
private void updateDefaultVolumes() {
- for (int stream = 0; stream < mStreamStates.length; stream++) {
- int streamVolumeAlias = mStreamVolumeAlias[stream];
+ for (int stream = 0; stream < mStreamStates.size(); stream++) {
+ int streamType = mStreamStates.keyAt(stream);
+ int streamVolumeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
if (mUseVolumeGroupAliases) {
- if (AudioSystem.DEFAULT_STREAM_VOLUME[stream] != UNSET_INDEX) {
+ if (AudioSystem.DEFAULT_STREAM_VOLUME[streamType] != UNSET_INDEX) {
// Already initialized through default property based mecanism.
continue;
}
streamVolumeAlias = AudioSystem.STREAM_MUSIC;
- int defaultAliasVolume = getUiDefaultRescaledIndex(streamVolumeAlias, stream);
- if ((defaultAliasVolume >= MIN_STREAM_VOLUME[stream])
- && (defaultAliasVolume <= MAX_STREAM_VOLUME[stream])) {
- AudioSystem.DEFAULT_STREAM_VOLUME[stream] = defaultAliasVolume;
+ int defaultAliasVolume = getUiDefaultRescaledIndex(streamVolumeAlias, streamType);
+ if ((defaultAliasVolume >= MIN_STREAM_VOLUME[streamType])
+ && (defaultAliasVolume <= MAX_STREAM_VOLUME[streamType])) {
+ AudioSystem.DEFAULT_STREAM_VOLUME[streamType] = defaultAliasVolume;
continue;
}
}
- if (streamVolumeAlias >= 0 && stream != streamVolumeAlias) {
- AudioSystem.DEFAULT_STREAM_VOLUME[stream] =
- getUiDefaultRescaledIndex(streamVolumeAlias, stream);
+ if (streamVolumeAlias >= 0 && streamType != streamVolumeAlias) {
+ AudioSystem.DEFAULT_STREAM_VOLUME[streamType] =
+ getUiDefaultRescaledIndex(streamVolumeAlias, streamType);
}
}
}
@@ -2490,13 +2548,17 @@
continue;
}
StringBuilder alias = new StringBuilder();
- if (mStreamVolumeAlias[i] != i) {
+ final int streamAlias = sStreamVolumeAlias.get(i, /*valueIfKeyNotFound*/-1);
+ if (streamAlias != i && streamAlias != -1) {
alias.append(" (aliased to: ")
- .append(AudioSystem.STREAM_NAMES[mStreamVolumeAlias[i]])
+ .append(AudioSystem.STREAM_NAMES[streamAlias])
.append(")");
}
pw.println("- " + AudioSystem.STREAM_NAMES[i] + alias + ":");
- mStreamStates[i].dump(pw);
+ final VolumeStreamState vss = getVssForStream(i);
+ if (vss != null) {
+ vss.dump(pw);
+ }
pw.println("");
}
pw.print("\n- mute affected streams = 0x");
@@ -2505,6 +2567,13 @@
pw.println(Integer.toHexString(mUserMutableStreams));
}
+ private void initStreamVolumeAlias(int[] streamVolumeAlias) {
+ sStreamVolumeAlias = new SparseIntArray(streamVolumeAlias.length);
+ for (int i = 0; i < streamVolumeAlias.length; ++i) {
+ sStreamVolumeAlias.put(i, streamVolumeAlias[i]);
+ }
+ }
+
private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
int dtmfStreamAlias;
final int a11yStreamAlias = sIndependentA11yVolume ?
@@ -2514,24 +2583,24 @@
AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
if (mIsSingleVolume) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION.clone();
+ initStreamVolumeAlias(STREAM_VOLUME_ALIAS_TELEVISION);
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
} else if (mUseVolumeGroupAliases) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE.clone();
+ initStreamVolumeAlias(STREAM_VOLUME_ALIAS_NONE);
dtmfStreamAlias = AudioSystem.STREAM_DTMF;
} else {
switch (mPlatformType) {
case AudioSystem.PLATFORM_VOICE:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE.clone();
+ initStreamVolumeAlias(STREAM_VOLUME_ALIAS_VOICE);
dtmfStreamAlias = AudioSystem.STREAM_RING;
break;
default:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT.clone();
+ initStreamVolumeAlias(STREAM_VOLUME_ALIAS_DEFAULT);
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
}
if (!mNotifAliasRing) {
- mStreamVolumeAlias[AudioSystem.STREAM_NOTIFICATION] =
- AudioSystem.STREAM_NOTIFICATION;
+ sStreamVolumeAlias.put(AudioSystem.STREAM_NOTIFICATION,
+ AudioSystem.STREAM_NOTIFICATION);
}
}
@@ -2546,15 +2615,14 @@
}
}
- mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
- mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
- mStreamVolumeAlias[AudioSystem.STREAM_ASSISTANT] = assistantStreamAlias;
+ sStreamVolumeAlias.put(AudioSystem.STREAM_DTMF, dtmfStreamAlias);
+ sStreamVolumeAlias.put(AudioSystem.STREAM_ACCESSIBILITY, a11yStreamAlias);
+ sStreamVolumeAlias.put(AudioSystem.STREAM_ASSISTANT, assistantStreamAlias);
if (replaceStreamBtSco()) {
// we do not support STREAM_BLUETOOTH_SCO, this will lead to having
- // mStreanStates[STREAM_BLUETOOTH_SCO] = null
- // TODO: replace arrays with SparseIntArrays to avoid null checks
- mStreamVolumeAlias[AudioSystem.STREAM_BLUETOOTH_SCO] = -1;
+ // mStreanStates.get(STREAM_BLUETOOTH_SCO) == null
+ sStreamVolumeAlias.delete(AudioSystem.STREAM_BLUETOOTH_SCO);
}
if (updateVolumes && mStreamStates != null) {
@@ -2562,17 +2630,17 @@
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
- mStreamStates[AudioSystem.STREAM_DTMF]
- .setAllIndexes(mStreamStates[dtmfStreamAlias], caller);
- mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setSettingName(
+ getVssForStreamOrDefault(AudioSystem.STREAM_DTMF)
+ .setAllIndexes(getVssForStreamOrDefault(dtmfStreamAlias), caller);
+ getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).setSettingName(
System.VOLUME_SETTINGS_INT[a11yStreamAlias]);
- mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes(
- mStreamStates[a11yStreamAlias], caller);
+ getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).setAllIndexes(
+ getVssForStreamOrDefault(a11yStreamAlias), caller);
}
}
if (sIndependentA11yVolume) {
// restore the a11y values from the settings
- mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].readSettings();
+ getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY).readSettings();
}
// apply stream mute states according to new value of mRingerModeAffectedStreams
@@ -2582,13 +2650,13 @@
SENDMSG_QUEUE,
0,
0,
- mStreamStates[AudioSystem.STREAM_DTMF], 0);
+ getVssForStreamOrDefault(AudioSystem.STREAM_DTMF), 0);
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
SENDMSG_QUEUE,
0,
0,
- mStreamStates[AudioSystem.STREAM_ACCESSIBILITY], 0);
+ getVssForStreamOrDefault(AudioSystem.STREAM_ACCESSIBILITY), 0);
}
dispatchStreamAliasingUpdate();
}
@@ -3065,7 +3133,8 @@
}
private int getIndexRange(int streamType) {
- return (mStreamStates[streamType].getMaxIndex() - mStreamStates[streamType].getMinIndex());
+ return (getVssForStreamOrDefault(streamType).getMaxIndex() - getVssForStreamOrDefault(
+ streamType).getMinIndex());
}
private int rescaleIndex(VolumeInfo volumeInfo, int dstStream) {
@@ -3073,11 +3142,12 @@
|| volumeInfo.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
|| volumeInfo.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
Log.e(TAG, "rescaleIndex: volumeInfo has invalid index or range");
- return mStreamStates[dstStream].getMinIndex();
+ return getVssForStreamOrDefault(dstStream).getMinIndex();
}
return rescaleIndex(volumeInfo.getVolumeIndex(),
volumeInfo.getMinVolumeIndex(), volumeInfo.getMaxVolumeIndex(),
- mStreamStates[dstStream].getMinIndex(), mStreamStates[dstStream].getMaxIndex());
+ getVssForStreamOrDefault(dstStream).getMinIndex(),
+ getVssForStreamOrDefault(dstStream).getMaxIndex());
}
private int rescaleIndex(int index, int srcStream, VolumeInfo dstVolumeInfo) {
@@ -3088,14 +3158,17 @@
return index;
}
return rescaleIndex(index,
- mStreamStates[srcStream].getMinIndex(), mStreamStates[srcStream].getMaxIndex(),
+ getVssForStreamOrDefault(srcStream).getMinIndex(),
+ getVssForStreamOrDefault(srcStream).getMaxIndex(),
dstMin, dstMax);
}
private int rescaleIndex(int index, int srcStream, int dstStream) {
return rescaleIndex(index,
- mStreamStates[srcStream].getMinIndex(), mStreamStates[srcStream].getMaxIndex(),
- mStreamStates[dstStream].getMinIndex(), mStreamStates[dstStream].getMaxIndex());
+ getVssForStreamOrDefault(srcStream).getMinIndex(),
+ getVssForStreamOrDefault(srcStream).getMaxIndex(),
+ getVssForStreamOrDefault(dstStream).getMinIndex(),
+ getVssForStreamOrDefault(dstStream).getMaxIndex());
}
private int rescaleIndex(int index, int srcMin, int srcMax, int dstMin, int dstMax) {
@@ -3618,7 +3691,7 @@
streamType = replaceBtScoStreamWithVoiceCall(streamType, "adjustSuggestedStreamVolume");
ensureValidStreamType(streamType);
- final int resolvedStream = mStreamVolumeAlias[streamType];
+ final int resolvedStream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
// Play sounds on STREAM_RING only.
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
@@ -3735,9 +3808,9 @@
// use stream type alias here so that streams with same alias have the same behavior,
// including with regard to silent mode control (e.g the use of STREAM_RING below and in
// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
- int streamTypeAlias = mStreamVolumeAlias[streamType];
+ int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
- VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+ VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
final int device = getDeviceForStream(streamTypeAlias);
@@ -3823,7 +3896,7 @@
if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
adjustVolume = false;
}
- int oldIndex = mStreamStates[streamType].getIndex(device);
+ int oldIndex = getVssForStreamOrDefault(streamType).getIndex(device);
// Check if the volume adjustment should be handled by an absolute volume controller instead
if (isAbsoluteVolumeDevice(device) && (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
@@ -3879,7 +3952,7 @@
0);
}
- int newIndex = mStreamStates[streamType].getIndex(device);
+ int newIndex = getVssForStreamOrDefault(streamType).getIndex(device);
int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() :
AudioSystem.STREAM_MUSIC;
@@ -3906,7 +3979,7 @@
+ newIndex + " stream=" + streamType);
}
mDeviceBroker.postSetLeAudioVolumeIndex(newIndex,
- mStreamStates[streamType].getMaxIndex(), streamType);
+ getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
}
// Check if volume update should be send to Hearing Aid.
@@ -3922,7 +3995,7 @@
}
}
- final int newIndex = mStreamStates[streamType].getIndex(device);
+ final int newIndex = getVssForStreamOrDefault(streamType).getIndex(device);
if (adjustVolume) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
@@ -4004,22 +4077,22 @@
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
List<Integer> streamsToMute = new ArrayList<>();
- for (int stream = 0; stream < mStreamStates.length; stream++) {
- VolumeStreamState vss = mStreamStates[stream];
- if (vss != null && streamAlias == mStreamVolumeAlias[stream]
+ for (int stream = 0; stream < mStreamStates.size(); stream++) {
+ final VolumeStreamState vss = mStreamStates.valueAt(stream);
+ if (vss != null && streamAlias == sStreamVolumeAlias.get(vss.getStreamType())
&& vss.isMutable()) {
if (!(mCameraSoundForced && (vss.getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
boolean changed = vss.mute(state, /* apply= */ false,
"muteAliasStreams");
if (changed) {
- streamsToMute.add(stream);
+ streamsToMute.add(vss.getStreamType());
}
}
}
}
streamsToMute.forEach(streamToMute -> {
- mStreamStates[streamToMute].doMute();
+ getVssForStreamOrDefault(streamToMute).doMute();
broadcastMuteSetting(streamToMute, state);
});
}
@@ -4047,7 +4120,7 @@
// vss.updateVolumeGroupIndex
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
- final VolumeStreamState streamState = mStreamStates[streamAlias];
+ final VolumeStreamState streamState = getVssForStreamOrDefault(streamAlias);
// if unmuting causes a change, it was muted
wasMuted = streamState.mute(false, "onUnmuteStreamOnSingleVolDevice");
if (wasMuted) {
@@ -4145,7 +4218,7 @@
*/
/*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
- final int stream = mStreamVolumeAlias[streamType];
+ final int stream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(stream == getUiSoundsStreamType())) {
@@ -4286,26 +4359,30 @@
// that can interfere with the sending of the VOLUME_CHANGED_ACTION intent
mAudioSystem.clearRoutingCache();
+ int streamType = replaceBtScoStreamWithVoiceCall(vi.getStreamType(), "setDeviceVolume");
+
+ final VolumeStreamState vss = getVssForStream(streamType);
+
// 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());
+ final int currDev = getDeviceForStream(streamType);
- final boolean skipping = (currDev == ada.getInternalType());
+ final boolean skipping = (currDev == ada.getInternalType()) || (vss == null);
- AudioService.sVolumeLogger.enqueue(new DeviceVolumeEvent(vi.getStreamType(), index, ada,
+ AudioService.sVolumeLogger.enqueue(new DeviceVolumeEvent(streamType, index, ada,
currDev, callingPackage, skipping));
if (skipping) {
- // setDeviceVolume was called on a device currently being used
+ // setDeviceVolume was called on a device currently being used or stream state is null
return;
}
// 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
- if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
- setStreamVolumeWithAttributionInt(vi.getStreamType(),
- mStreamStates[vi.getStreamType()].getMinIndex(),
+ if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(streamType)) {
+ setStreamVolumeWithAttributionInt(streamType,
+ vss.getMinIndex(),
/*flags*/ 0,
ada, callingPackage, null,
//TODO handle unmuting of current audio device
@@ -4319,22 +4396,22 @@
if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
|| vi.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
// assume index meant to be in stream type range, validate
- if ((index * 10) < mStreamStates[vi.getStreamType()].getMinIndex()
- || (index * 10) > mStreamStates[vi.getStreamType()].getMaxIndex()) {
+ if ((index * 10) < vss.getMinIndex()
+ || (index * 10) > vss.getMaxIndex()) {
throw new IllegalArgumentException("invalid volume index " + index
+ " not between min/max for stream " + vi.getStreamType());
}
} else {
// check if index needs to be rescaled
- final int min = (mStreamStates[vi.getStreamType()].getMinIndex() + 5) / 10;
- final int max = (mStreamStates[vi.getStreamType()].getMaxIndex() + 5) / 10;
+ final int min = (vss.getMinIndex() + 5) / 10;
+ final int max = (vss.getMaxIndex() + 5) / 10;
if (vi.getMinVolumeIndex() != min || vi.getMaxVolumeIndex() != max) {
index = rescaleIndex(index,
/*srcMin*/ vi.getMinVolumeIndex(), /*srcMax*/ vi.getMaxVolumeIndex(),
/*dstMin*/ min, /*dstMax*/ max);
}
}
- setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
+ setStreamVolumeWithAttributionInt(streamType, index, /*flags*/ 0,
ada, callingPackage, null,
false /*canChangeMuteAndUpdateController*/);
}
@@ -4762,7 +4839,7 @@
if (AudioSystem.isLeAudioDeviceType(device)) {
mDeviceBroker.postSetLeAudioVolumeIndex(index * 10,
- mStreamStates[streamType].getMaxIndex(), streamType);
+ getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
} else {
@@ -4790,8 +4867,8 @@
streamType = replaceBtScoStreamWithVoiceCall(streamType, "setStreamVolume");
ensureValidStreamType(streamType);
- int streamTypeAlias = mStreamVolumeAlias[streamType];
- VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+ int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1);
+ final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
&& isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
@@ -4857,7 +4934,7 @@
// ada is non-null when called from setDeviceVolume,
// which shouldn't update the mute state
canChangeMuteAndUpdateController /*canChangeMute*/);
- index = mStreamStates[streamType].getIndex(device);
+ index = getVssForStreamOrDefault(streamType).getIndex(device);
}
}
@@ -4885,8 +4962,8 @@
Log.d(TAG, "setStreamVolume postSetLeAudioVolumeIndex index="
+ index + " stream=" + streamType);
}
- mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(),
- streamType);
+ mDeviceBroker.postSetLeAudioVolumeIndex(index,
+ getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
}
if (device == AudioSystem.DEVICE_OUT_HEARING_AID
@@ -4916,7 +4993,7 @@
// ada is non-null when called from setDeviceVolume,
// which shouldn't update the mute state
canChangeMuteAndUpdateController /*canChangeMute*/);
- index = mStreamStates[streamType].getIndex(device);
+ index = getVssForStreamOrDefault(streamType).getIndex(device);
}
}
@@ -5099,7 +5176,7 @@
// UI update and Broadcast Intent
protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device)
{
- streamType = mStreamVolumeAlias[streamType];
+ streamType = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
if (streamType == AudioSystem.STREAM_MUSIC && isFullVolumeDevice(device)) {
flags &= ~AudioManager.FLAG_SHOW_UI;
@@ -5147,7 +5224,7 @@
if (isFullVolumeDevice(device)) {
return;
}
- VolumeStreamState streamState = mStreamStates[streamType];
+ final VolumeStreamState streamState = getVssForStreamOrDefault(streamType);
if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) {
// Post message to set system volume (it in turn will post a message
@@ -5171,7 +5248,7 @@
synchronized (VolumeStreamState.class) {
ensureValidStreamType(streamType);
- return mStreamStates[streamType].mIsMuted;
+ return getVssForStreamOrDefault(streamType).mIsMuted;
}
}
@@ -5270,7 +5347,7 @@
if (applyRequired) {
// Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX
checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC);
- mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes();
+ getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).applyAllVolumes();
}
}
}
@@ -5351,15 +5428,16 @@
private int getStreamVolume(int streamType, int device) {
synchronized (VolumeStreamState.class) {
- int index = mStreamStates[streamType].getIndex(device);
+ final VolumeStreamState vss = getVssForStreamOrDefault(streamType);
+ int index = vss.getIndex(device);
// by convention getStreamVolume() returns 0 when a stream is muted.
- if (mStreamStates[streamType].mIsMuted) {
+ if (vss.mIsMuted) {
index = 0;
}
- if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
- isFixedVolumeDevice(device)) {
- index = mStreamStates[streamType].getMaxIndex();
+ if (index != 0 && (sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC)
+ && isFixedVolumeDevice(device)) {
+ index = vss.getMaxIndex();
}
return (index + 5) / 10;
}
@@ -5382,20 +5460,27 @@
return getDefaultVolumeInfo();
}
- int streamType = vi.getStreamType();
+ int streamType = replaceBtScoStreamWithVoiceCall(vi.getStreamType(), "getStreamMaxVolume");
final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi);
- vib.setMinVolumeIndex((mStreamStates[streamType].mIndexMin + 5) / 10);
- vib.setMaxVolumeIndex((mStreamStates[streamType].mIndexMax + 5) / 10);
+ final VolumeStreamState vss = getVssForStream(streamType);
+ if (vss == null) {
+ Log.w(TAG,
+ "getDeviceVolume unsupported stream type " + streamType + ". Return default");
+ return getDefaultVolumeInfo();
+ }
+
+ vib.setMinVolumeIndex((vss.mIndexMin + 5) / 10);
+ vib.setMaxVolumeIndex((vss.mIndexMax + 5) / 10);
synchronized (VolumeStreamState.class) {
final int index;
if (isFixedVolumeDevice(ada.getInternalType())) {
- index = (mStreamStates[streamType].mIndexMax + 5) / 10;
+ index = (vss.mIndexMax + 5) / 10;
} else {
- index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10;
+ index = (vss.getIndex(ada.getInternalType()) + 5) / 10;
}
vib.setVolumeIndex(index);
// only set as a mute command if stream muted
- if (mStreamStates[streamType].mIsMuted) {
+ if (vss.mIsMuted) {
vib.setMuted(true);
}
return vib.build();
@@ -5406,7 +5491,7 @@
public int getStreamMaxVolume(int streamType) {
streamType = replaceBtScoStreamWithVoiceCall(streamType, "getStreamMaxVolume");
ensureValidStreamType(streamType);
- return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
+ return (getVssForStreamOrDefault(streamType).getMaxIndex() + 5) / 10;
}
/** @see AudioManager#getStreamMinVolumeInt(int)
@@ -5419,7 +5504,7 @@
|| callingHasAudioSettingsPermission()
|| (mContext.checkCallingPermission(MODIFY_AUDIO_ROUTING)
== PackageManager.PERMISSION_GRANTED);
- return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10;
+ return (getVssForStreamOrDefault(streamType).getMinIndex(isPrivileged) + 5) / 10;
}
@android.annotation.EnforcePermission(QUERY_AUDIO_STATE)
@@ -5432,7 +5517,7 @@
ensureValidStreamType(streamType);
int device = getDeviceForStream(streamType);
- return (mStreamStates[streamType].getIndex(device) + 5) / 10;
+ return (getVssForStreamOrDefault(streamType).getIndex(device) + 5) / 10;
}
/**
@@ -5502,9 +5587,10 @@
.boxed().toList());
}
ArrayList<Integer> res = new ArrayList(1);
- for (int stream : mStreamVolumeAlias) {
- if (stream >= 0 && !res.contains(stream)) {
- res.add(stream);
+ for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
+ final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+ if (!res.contains(streamAlias)) {
+ res.add(streamAlias);
}
}
return res;
@@ -5525,7 +5611,7 @@
// verify parameters
ensureValidStreamType(sourceStreamType);
- return mStreamVolumeAlias[sourceStreamType];
+ return sStreamVolumeAlias.get(sourceStreamType, /*valueIfKeyNotFound=*/-1);
}
/**
@@ -5547,7 +5633,7 @@
*/
public int getUiSoundsStreamType() {
return mUseVolumeGroupAliases ? STREAM_VOLUME_ALIAS_VOICE[AudioSystem.STREAM_SYSTEM]
- : mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
+ : sStreamVolumeAlias.get(AudioSystem.STREAM_SYSTEM);
}
/**
@@ -5559,7 +5645,7 @@
return mUseVolumeGroupAliases
? STREAM_VOLUME_ALIAS_VOICE[aliasStreamType]
== STREAM_VOLUME_ALIAS_VOICE[AudioSystem.STREAM_SYSTEM]
- : aliasStreamType == mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
+ : aliasStreamType == sStreamVolumeAlias.get(AudioSystem.STREAM_SYSTEM);
}
/** @see AudioManager#setMicrophoneMute(boolean) */
@@ -5853,6 +5939,10 @@
forceUse, eventSource, 0);
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ final VolumeStreamState vss = getVssForStream(streamType);
+ if (vss == null) {
+ continue;
+ }
final boolean isMuted = isStreamMutedByRingerOrZenMode(streamType);
final boolean muteAllowedBySco =
!((shouldRingSco || shouldRingBle) && streamType == AudioSystem.STREAM_RING);
@@ -5863,10 +5953,9 @@
if (!shouldMute) {
// unmute
// ring and notifications volume should never be 0 when not silenced
- if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING
- || mStreamVolumeAlias[streamType] == AudioSystem.STREAM_NOTIFICATION) {
+ if (sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_RING
+ || sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_NOTIFICATION) {
synchronized (VolumeStreamState.class) {
- final VolumeStreamState vss = mStreamStates[streamType];
for (int i = 0; i < vss.mIndexMap.size(); i++) {
int device = vss.mIndexMap.keyAt(i);
int value = vss.mIndexMap.valueAt(i);
@@ -5881,20 +5970,20 @@
SENDMSG_QUEUE,
device,
0,
- mStreamStates[streamType],
+ vss,
PERSIST_DELAY);
}
}
sRingerAndZenModeMutedStreams &= ~(1 << streamType);
sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
- mStreamStates[streamType].mute(false, "muteRingerModeStreams");
+ vss.mute(false, "muteRingerModeStreams");
} else {
// mute
sRingerAndZenModeMutedStreams |= (1 << streamType);
sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
- mStreamStates[streamType].mute(true, "muteRingerModeStreams");
+ vss.mute(true, "muteRingerModeStreams");
}
}
}
@@ -6317,15 +6406,15 @@
final int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
final int device = getDeviceForStream(streamType);
- final int streamAlias = mStreamVolumeAlias[streamType];
+ final int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/
+ -1);
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: streamType=" + streamType
+ ", streamAlias=" + streamAlias);
}
- final int index = mStreamStates[streamAlias].getIndex(device);
- final int maxIndex = mStreamStates[streamAlias].getMaxIndex();
+ final int index = getVssForStreamOrDefault(streamAlias).getIndex(device);
setStreamVolumeInt(streamAlias, index, device, true,
requesterPackage, true /*hasModifyAudioSettings*/);
@@ -6630,13 +6719,13 @@
// restore volume settings
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
- VolumeStreamState streamState = mStreamStates[streamType];
+ final VolumeStreamState streamState = getVssForStream(streamType);
if (streamState == null) {
continue;
}
- if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) {
+ if (userSwitch && sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC) {
continue;
}
@@ -7204,7 +7293,7 @@
} else {
ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
}
- if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
+ if (sStreamVolumeAlias.get(AudioSystem.STREAM_DTMF) == AudioSystem.STREAM_RING) {
ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
} else {
ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
@@ -7260,7 +7349,7 @@
}
private void ensureValidStreamType(int streamType) {
- if (streamType < 0 || streamType >= mStreamStates.length) {
+ if (streamType < 0 || streamType >= AudioSystem.getNumStreamTypes()) {
throw new IllegalArgumentException("Bad stream type " + streamType);
}
}
@@ -7525,9 +7614,10 @@
? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
: Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
// update the VolumeStreamState for STREAM_ALARM and its aliases
- for (int stream : mStreamVolumeAlias) {
- if (stream >= 0 && mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) {
- mStreamStates[stream].updateNoPermMinIndex(safeIndex);
+ for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
+ final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+ if (streamAlias == AudioSystem.STREAM_ALARM) {
+ getVssForStreamOrDefault(streamAlias).updateNoPermMinIndex(safeIndex);
}
}
}
@@ -7642,21 +7732,21 @@
stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceSetForStream");
ensureValidStreamType(stream);
synchronized (VolumeStreamState.class) {
- return mStreamStates[stream].observeDevicesForStream_syncVSS(true);
+ return getVssForStreamOrDefault(stream).observeDevicesForStream_syncVSS(true);
}
}
private void onObserveDevicesForAllStreams(int skipStream) {
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
- for (int stream = 0; stream < mStreamStates.length; stream++) {
- if (stream != skipStream && mStreamStates[stream] != null) {
+ for (int stream = 0; stream < mStreamStates.size(); stream++) {
+ final VolumeStreamState vss = mStreamStates.valueAt(stream);
+ if (vss != null && vss.getStreamType() != skipStream) {
Set<Integer> deviceSet =
- mStreamStates[stream].observeDevicesForStream_syncVSS(
- false /*checkOthers*/);
+ vss.observeDevicesForStream_syncVSS(false /*checkOthers*/);
for (Integer device : deviceSet) {
// Update volume states for devices routed for the stream
- updateVolumeStates(device, stream,
+ updateVolumeStates(device, vss.getStreamType(),
"AudioService#onObserveDevicesForAllStreams");
}
}
@@ -7689,7 +7779,7 @@
private void onUpdateScoDeviceActive(boolean scoDeviceActive) {
if (mScoDeviceActive.compareAndSet(!scoDeviceActive, scoDeviceActive)) {
- getVssVolumeForStream(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
+ getVssForStreamOrDefault(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
}
}
@@ -8084,7 +8174,7 @@
/** only public for mocking/spying, do not call outside of AudioService */
@VisibleForTesting
public void setMusicMute(boolean mute) {
- mStreamStates[AudioSystem.STREAM_MUSIC].muteInternally(mute);
+ getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).muteInternally(mute);
}
private static final Set<Integer> DEVICE_MEDIA_UNMUTED_ON_PLUG_SET;
@@ -8115,8 +8205,8 @@
if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
&& !isStreamMutedByRingerOrZenMode(AudioSystem.STREAM_MUSIC)
&& DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.contains(newDevice)
- && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
- && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
+ && getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).mIsMuted
+ && getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).getIndex(newDevice) != 0
&& getDeviceSetForStreamDirect(AudioSystem.STREAM_MUSIC).contains(newDevice)) {
if (DEBUG_VOL) {
Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
@@ -8125,7 +8215,8 @@
// Locking mSettingsLock to avoid inversion when calling vss.mute -> vss.doMute ->
// vss.updateVolumeGroupIndex
synchronized (mSettingsLock) {
- mStreamStates[AudioSystem.STREAM_MUSIC].mute(false, "onAccessoryPlugMediaUnmute");
+ getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC).mute(false,
+ "onAccessoryPlugMediaUnmute");
}
}
}
@@ -8296,8 +8387,8 @@
}
if (replaceStreamBtSco()) {
- mIndexMin = mStreamStates[mPublicStreamType].getMinIndex() / 10;
- mIndexMax = mStreamStates[mPublicStreamType].getMaxIndex() / 10;
+ mIndexMin = getVssForStreamOrDefault(mPublicStreamType).getMinIndex() / 10;
+ mIndexMax = getVssForStreamOrDefault(mPublicStreamType).getMaxIndex() / 10;
} else {
mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
@@ -8334,7 +8425,7 @@
*/
private boolean isVssMuteBijective(int stream) {
return isStreamAffectedByMute(stream)
- && (getMinIndex() == (mStreamStates[stream].getMinIndex() + 5) / 10)
+ && (getMinIndex() == (getVssForStreamOrDefault(stream).getMinIndex() + 5) / 10)
&& (getMinIndex() == 0 || isCallStream(stream));
}
@@ -8380,7 +8471,8 @@
return;
}
- float stepFactor = mStreamStates[mPublicStreamType].getIndexStepFactor();
+ float stepFactor = getVssForStreamOrDefault(
+ mPublicStreamType).getIndexStepFactor();
switch (direction) {
case AudioManager.ADJUST_TOGGLE_MUTE: {
// Note: If muted by volume 0, unmute will restore volume 0.
@@ -8476,7 +8568,7 @@
// This allows RX path muting by the audio HAL only when explicitly muted but not when
// index is just set to 0 to repect BT requirements
if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
- && mStreamStates[mPublicStreamType].isFullyMuted()) {
+ && getVssForStreamOrDefault(mPublicStreamType).isFullyMuted()) {
index = 0;
} else if (isStreamBluetoothSco(mPublicStreamType) && index == 0) {
index = 1;
@@ -8484,7 +8576,7 @@
if (replaceStreamBtSco()) {
index = (int) (mIndexMin + (index - mIndexMin)
- / mStreamStates[mPublicStreamType].getIndexStepFactor());
+ / getVssForStreamOrDefault(mPublicStreamType).getIndexStepFactor());
}
if (DEBUG_VOL) {
@@ -8517,7 +8609,7 @@
}
private boolean isValidStream(int stream) {
- return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length);
+ return (stream != AudioSystem.STREAM_DEFAULT) && getVssForStream(stream) != null;
}
public boolean isMusic() {
@@ -8535,10 +8627,10 @@
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
for (int stream : getLegacyStreamTypes()) {
if (isValidStream(stream)) {
- boolean streamMuted = mStreamStates[stream].mIsMuted;
+ final VolumeStreamState vss = getVssForStreamOrDefault(stream);
+ boolean streamMuted = vss.mIsMuted;
int deviceForStream = getDeviceForStream(stream);
- int indexForStream =
- (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10;
+ int indexForStream = (vss.getIndex(deviceForStream) + 5) / 10;
if (device == deviceForStream) {
if (indexForStream == index && (isMuted() == streamMuted)
&& isVssMuteBijective(stream)) {
@@ -8548,19 +8640,17 @@
if (vgsVssSyncMuteOrder()) {
if ((isMuted() != streamMuted) && isVssMuteBijective(
stream)) {
- mStreamStates[stream].mute(isMuted(),
- "VGS.applyAllVolumes#1");
+ vss.mute(isMuted(), "VGS.applyAllVolumes#1");
}
}
if (indexForStream != index) {
- mStreamStates[stream].setIndex(index * 10, device, caller,
- true /*hasModifyAudioSettings*/);
+ vss.setIndex(index * 10, device,
+ caller, true /*hasModifyAudioSettings*/);
}
if (!vgsVssSyncMuteOrder()) {
if ((isMuted() != streamMuted) && isVssMuteBijective(
stream)) {
- mStreamStates[stream].mute(isMuted(),
- "VGS.applyAllVolumes#1");
+ vss.mute(isMuted(), "VGS.applyAllVolumes#1");
}
}
}
@@ -8584,11 +8674,12 @@
boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0);
for (int stream : getLegacyStreamTypes()) {
if (isValidStream(stream)) {
- boolean streamMuted = mStreamStates[stream].mIsMuted;
- int defaultStreamIndex = (mStreamStates[stream].getIndex(
- AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
+ final VolumeStreamState vss = getVssForStreamOrDefault(stream);
+ boolean streamMuted = vss.mIsMuted;
+ int defaultStreamIndex = (vss.getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)
+ / 10;
if (forceDeviceSync) {
- mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller,
+ vss.setIndex(index * 10, deviceForVolume, caller,
true /*hasModifyAudioSettings*/);
}
if (defaultStreamIndex == index && (isMuted() == streamMuted)
@@ -8597,12 +8688,11 @@
continue;
}
if (defaultStreamIndex != index) {
- mStreamStates[stream].setIndex(
- index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
+ vss.setIndex(index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
true /*hasModifyAudioSettings*/);
}
if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
- mStreamStates[stream].mute(isMuted(), "VGS.applyAllVolumes#2");
+ vss.mute(isMuted(), "VGS.applyAllVolumes#2");
}
}
}
@@ -8950,7 +9040,7 @@
postObserveDevicesForAllStreams(mStreamType);
}
// log base stream changes to the event log
- if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+ if (sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1) == mStreamType) {
EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices);
}
// send STREAM_DEVICES_CHANGED_ACTION on the message handler so it is scheduled after
@@ -9202,10 +9292,11 @@
isCurrentDevice = (device == getDeviceForStream(mStreamType));
final int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- final VolumeStreamState aliasStreamState = mStreamStates[streamType];
- if (streamType != mStreamType &&
- mStreamVolumeAlias[streamType] == mStreamType &&
- (changed || !aliasStreamState.hasIndexForDevice(device))) {
+ final VolumeStreamState aliasStreamState = getVssForStream(streamType);
+ if (aliasStreamState != null && streamType != mStreamType
+ && sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1)
+ == mStreamType && (changed || !aliasStreamState.hasIndexForDevice(
+ device))) {
final int scaledIndex =
rescaleIndex(aliasIndex, mStreamType, streamType);
boolean changedAlias = aliasStreamState.setIndex(scaledIndex, device,
@@ -9240,7 +9331,7 @@
oldIndex = (oldIndex + 5) / 10;
index = (index + 5) / 10;
// log base stream changes to the event log
- if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+ if (sStreamVolumeAlias.get(mStreamType, /*valueIfKeyNotFound=*/-1) == mStreamType) {
if (caller == null) {
Log.w(TAG, "No caller for volume_changed event", new Throwable());
}
@@ -9252,7 +9343,9 @@
if ((index != oldIndex) && isCurrentDevice) {
// for single volume devices, only send the volume change broadcast
// on the alias stream
- if (!mIsSingleVolume || (mStreamVolumeAlias[mStreamType] == mStreamType)) {
+ final int streamAlias = sStreamVolumeAlias.get(
+ mStreamType, /*valueIfKeyNotFound=*/-1);
+ if (!mIsSingleVolume || streamAlias == mStreamType) {
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
oldIndex);
@@ -9267,9 +9360,9 @@
mStreamType);
}
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
- mStreamVolumeAlias[mStreamType]);
+ streamAlias);
- if (mStreamType == mStreamVolumeAlias[mStreamType]) {
+ if (mStreamType == streamAlias) {
String aliasStreamIndexesString = "";
if (!aliasStreamIndexes.isEmpty()) {
aliasStreamIndexesString =
@@ -9527,7 +9620,7 @@
public void checkFixedVolumeDevices() {
synchronized (VolumeStreamState.class) {
// ignore settings for fixed volume devices: volume should always be at max or 0
- if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
+ if (sStreamVolumeAlias.get(mStreamType) == AudioSystem.STREAM_MUSIC) {
for (int i = 0; i < mIndexMap.size(); i++) {
int device = mIndexMap.keyAt(i);
int index = mIndexMap.valueAt(i);
@@ -9673,7 +9766,11 @@
}
private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) {
- final VolumeStreamState streamState = mStreamStates[update.mStreamType];
+ final VolumeStreamState streamState = getVssForStream(update.mStreamType);
+ if (streamState == null) {
+ Log.w(TAG, "Invalid onSetVolumeIndexOnDevice for stream type " + update.mStreamType);
+ return;
+ }
if (update.hasVolumeIndex()) {
int index = update.getVolumeIndex();
if (mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
@@ -9704,8 +9801,10 @@
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- if (streamType != streamState.mStreamType &&
- mStreamVolumeAlias[streamType] == streamState.mStreamType) {
+ final VolumeStreamState vss = getVssForStream(streamType);
+ if (vss != null && streamType != streamState.mStreamType
+ && sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1)
+ == streamState.mStreamType) {
// Make sure volume is also maxed out on A2DP device for aliased stream
// that may have a different device selected
int streamDevice = getDeviceForStream(streamType);
@@ -9713,9 +9812,9 @@
&& (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device))) {
- mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+ vss.applyDeviceVolume_syncVSS(device);
}
- mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+ vss.applyDeviceVolume_syncVSS(streamDevice);
}
}
}
@@ -9749,9 +9848,11 @@
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- if (streamType != streamState.mStreamType &&
- mStreamVolumeAlias[streamType] == streamState.mStreamType) {
- mStreamStates[streamType].applyAllVolumes();
+ final VolumeStreamState vss = getVssForStream(streamType);
+ if (vss != null && streamType != streamState.mStreamType
+ && sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1)
+ == streamState.mStreamType) {
+ vss.applyAllVolumes();
}
}
}
@@ -10201,7 +10302,7 @@
}
sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
- mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+ getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC), 0);
}
/**
@@ -10321,7 +10422,7 @@
SENDMSG_QUEUE,
0,
0,
- mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+ getVssForStreamOrDefault(AudioSystem.STREAM_MUSIC), 0);
} else if (action.equals(Intent.ACTION_USER_BACKGROUND)) {
// Disable audio recording for the background user/profile
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
@@ -11488,13 +11589,15 @@
if (cameraSoundForcedChanged) {
if (!mIsSingleVolume) {
synchronized (VolumeStreamState.class) {
- VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
+ final VolumeStreamState s = getVssForStreamOrDefault(
+ AudioSystem.STREAM_SYSTEM_ENFORCED);
if (cameraSoundForced) {
s.setAllIndexesToMax();
mRingerModeAffectedStreams &=
~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
} else {
- s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG);
+ s.setAllIndexes(getVssForStreamOrDefault(AudioSystem.STREAM_SYSTEM),
+ TAG);
mRingerModeAffectedStreams |=
(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
}
@@ -11511,7 +11614,7 @@
SENDMSG_QUEUE,
0,
0,
- mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+ getVssForStreamOrDefault(AudioSystem.STREAM_SYSTEM_ENFORCED), 0);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index ded93e6..dc79ab2 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -633,7 +633,7 @@
}
/*package*/ void enforceSafeMediaVolume(String caller) {
- AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
+ AudioService.VolumeStreamState streamState = mAudioService.getVssForStreamOrDefault(
AudioSystem.STREAM_MUSIC);
for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i) {
@@ -665,7 +665,7 @@
@GuardedBy("mSafeMediaVolumeStateLock")
private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
- && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
+ && (AudioService.sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC)
&& safeDevicesContains(device)
&& (index > safeMediaVolumeIndex(device));
}
@@ -908,7 +908,7 @@
return;
}
- if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
+ if (AudioService.sStreamVolumeAlias.get(streamType) == AudioSystem.STREAM_MUSIC
&& safeDevicesContains(device)) {
float attenuationDb = -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
(newIndex + 5) / 10, device);
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 222c5a8..dc611fc 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -332,6 +332,16 @@
}
/**
+ * Sets the {@link BrightnessReason} using the int-based reason enum. This is a convenience
+ * function so we don't have to type out the constructor syntax everywhere.
+ *
+ * @param brightnessReason The int-based brightness enum.
+ */
+ public Builder setBrightnessReason(int brightnessReason) {
+ return setBrightnessReason(new BrightnessReason(brightnessReason));
+ }
+
+ /**
* Gets the {@link com.android.server.display.brightness.strategy.DisplayBrightnessStrategy}
* name
*/
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index 9bf10a7..9a0ee03 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -16,6 +16,7 @@
package com.android.server.display.brightness;
+import android.annotation.Nullable;
import android.util.Slog;
import java.util.Objects;
@@ -66,6 +67,16 @@
// Any number of MODIFIER_*
private int mModifier;
+ // Tag used to identify the source of the brightness (usually a specific activity/window).
+ private CharSequence mTag;
+
+ public BrightnessReason() {
+ }
+
+ public BrightnessReason(int reason) {
+ setReason(reason);
+ }
+
/**
* A utility to clone a BrightnessReason from another BrightnessReason event
*
@@ -74,6 +85,7 @@
public void set(BrightnessReason other) {
setReason(other == null ? REASON_UNKNOWN : other.mReason);
setModifier(other == null ? 0 : other.mModifier);
+ setTag(other == null ? null : other.mTag);
}
/**
@@ -85,19 +97,20 @@
setModifier(modifier | this.mModifier);
}
-
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BrightnessReason)) {
return false;
}
BrightnessReason other = (BrightnessReason) obj;
- return other.mReason == mReason && other.mModifier == mModifier;
+ return other.mReason == mReason
+ && other.mModifier == mModifier
+ && Objects.equals(other.mTag != null ? other.mTag.toString() : null, mTag);
}
@Override
public int hashCode() {
- return Objects.hash(mReason, mModifier);
+ return Objects.hash(mReason, mModifier, mTag);
}
@Override
@@ -115,6 +128,11 @@
public String toString(int adjustments) {
final StringBuilder sb = new StringBuilder();
sb.append(reasonToString(mReason));
+
+ if (mTag != null) {
+ sb.append("(").append(mTag).append(")");
+ }
+
sb.append(" [");
if ((adjustments & ADJUSTMENT_AUTO_TEMP) != 0) {
sb.append(" temp_adj");
@@ -149,8 +167,23 @@
return sb.toString();
}
+ public void setTag(@Nullable CharSequence tag) {
+ mTag = tag;
+ }
+
/**
- * A utility to set the reason of the BrightnessReason object
+ * Gets the tag to identify who requested the brightness.
+ */
+ @Nullable public CharSequence getTag() {
+ return mTag;
+ }
+
+ public int getReason() {
+ return mReason;
+ }
+
+ /**
+ * Sets the reason of the BrightnessReason object
*
* @param reason The value to which the reason is to be updated.
*/
@@ -162,16 +195,12 @@
}
}
- public int getReason() {
- return mReason;
- }
-
public int getModifier() {
return mModifier;
}
/**
- * A utility to set the modified of the current BrightnessReason object
+ * Sets the modifier bitflags of the current BrightnessReason object
*
* @param modifier The value to which the modifier is to be updated
*/
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 40a495c..3fc15d1 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -16,9 +16,10 @@
package com.android.server.display.brightness.strategy;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
-import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.StrategyExecutionRequest;
import com.android.server.display.brightness.StrategySelectionNotifyRequest;
@@ -33,9 +34,14 @@
StrategyExecutionRequest strategyExecutionRequest) {
// Todo(b/241308599): Introduce a validator class and add validations before setting
// the brightness
- return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_OVERRIDE,
- strategyExecutionRequest.getDisplayPowerRequest().screenBrightnessOverride,
- getName());
+ DisplayPowerRequest dpr = strategyExecutionRequest.getDisplayPowerRequest();
+ BrightnessReason reason = new BrightnessReason(BrightnessReason.REASON_OVERRIDE);
+ reason.setTag(dpr.screenBrightnessOverrideTag);
+ return new DisplayBrightnessState.Builder()
+ .setBrightness(dpr.screenBrightnessOverride)
+ .setBrightnessReason(reason)
+ .setDisplayBrightnessStrategyName(getName())
+ .build();
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0940544..ed71765 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -170,6 +170,7 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.AccessibilityManagerInternal;
@@ -5700,24 +5701,12 @@
@GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
@UserIdInt int userId) {
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
- if (!settings.getMethodMap().containsKey(imeId)
- || !settings.getEnabledInputMethodList()
- .contains(settings.getMethodMap().get(imeId))) {
- return false; // IME is not found or not enabled.
- }
- setInputMethodLocked(imeId, subtypeId, userId);
- return true;
- }
- if (!settings.getMethodMap().containsKey(imeId)
- || !settings.getEnabledInputMethodList().contains(
- settings.getMethodMap().get(imeId))) {
+ final var settings = InputMethodSettingsRepository.get(userId);
+ final var enabledImes = settings.getEnabledInputMethodList();
+ if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) {
return false; // IME is not found or not enabled.
}
- settings.putSelectedInputMethod(imeId);
- // For non-current user, only reset subtypeId (instead of setting the given one).
- settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ setInputMethodLocked(imeId, subtypeId, userId);
return true;
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d9f3622..9acf030 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -292,7 +292,7 @@
== PackageManager.PERMISSION_GRANTED;
final boolean hasModifyAudioRoutingPermission =
checkCallerHasModifyAudioRoutingPermission(pid, uid);
-
+ boolean hasMediaContentControlPermission = checkMediaContentControlPermission(uid, pid);
boolean hasMediaRoutingControlPermission =
checkMediaRoutingControlPermission(uid, pid, packageName);
@@ -307,6 +307,7 @@
userId,
hasConfigureWifiDisplayPermission,
hasModifyAudioRoutingPermission,
+ hasMediaContentControlPermission,
hasMediaRoutingControlPermission);
}
} finally {
@@ -1133,6 +1134,7 @@
int userId,
boolean hasConfigureWifiDisplayPermission,
boolean hasModifyAudioRoutingPermission,
+ boolean hasMediaContentControlPermission,
boolean hasMediaRoutingControlPermission) {
final IBinder binder = router.asBinder();
if (mAllRouterRecords.get(binder) != null) {
@@ -1151,6 +1153,7 @@
packageName,
hasConfigureWifiDisplayPermission,
hasModifyAudioRoutingPermission,
+ hasMediaContentControlPermission,
hasMediaRoutingControlPermission);
try {
binder.linkToDeath(routerRecord, 0);
@@ -2067,9 +2070,10 @@
public final int mPid;
public final boolean mHasConfigureWifiDisplayPermission;
public final boolean mHasModifyAudioRoutingPermission;
+ public final boolean mHasMediaContentControlPermission;
+ public final boolean mHasMediaRoutingControl;
public final AtomicBoolean mHasBluetoothRoutingPermission;
public final int mRouterId;
- public final boolean mHasMediaRoutingControl;
public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
public RouteDiscoveryPreference mDiscoveryPreference;
@@ -2083,6 +2087,7 @@
String packageName,
boolean hasConfigureWifiDisplayPermission,
boolean hasModifyAudioRoutingPermission,
+ boolean hasMediaContentControlPermission,
boolean hasMediaRoutingControl) {
mUserRecord = userRecord;
mPackageName = packageName;
@@ -2093,9 +2098,10 @@
mPid = pid;
mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
+ mHasMediaContentControlPermission = hasMediaContentControlPermission;
+ mHasMediaRoutingControl = hasMediaRoutingControl;
mHasBluetoothRoutingPermission =
new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
- mHasMediaRoutingControl = hasMediaRoutingControl;
mRouterId = mNextRouterOrManagerId.getAndIncrement();
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 77bdc45..72594b3 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -430,8 +430,8 @@
return mDisplayPowerRequest.policy;
}
- boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
- boolean boostScreenBrightness, int dozeScreenState,
+ boolean updateLocked(float screenBrightnessOverride, CharSequence overrideTag,
+ boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
@Display.StateReason int dozeScreenStateReason,
float dozeScreenBrightness, boolean overrideDrawWakeLock,
PowerSaveState powerSaverState, boolean quiescent,
@@ -441,6 +441,7 @@
mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
+ mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
mDisplayPowerRequest.useProximitySensor = useProximitySensor;
mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6fe1ccd..10faf14 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -629,6 +629,9 @@
private float mScreenBrightnessOverrideFromWindowManager =
PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ // Tag identifying the window/activity that requested the brightness override.
+ private CharSequence mScreenBrightnessOverrideFromWmTag = null;
+
// The window manager has determined the user to be inactive via other means.
// Set this to false to disable.
private boolean mUserInactiveOverrideFromWindowManager;
@@ -3623,16 +3626,18 @@
// Determine appropriate screen brightness.
final float screenBrightnessOverride;
+ CharSequence overrideTag = null;
if (!mBootCompleted) {
// Keep the brightness steady during boot. This requires the
// bootloader brightness and the default brightness to be identical.
screenBrightnessOverride = mScreenBrightnessDefault;
} else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
+ overrideTag = mScreenBrightnessOverrideFromWmTag;
} else {
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
+ boolean ready = powerGroup.updateLocked(screenBrightnessOverride, overrideTag,
shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
mDozeScreenStateOverrideFromDreamManager,
mDozeScreenStateOverrideReasonFromDreamManager,
@@ -4417,11 +4422,13 @@
}
}
- private void setScreenBrightnessOverrideFromWindowManagerInternal(float brightness) {
+ private void setScreenBrightnessOverrideFromWindowManagerInternal(
+ float brightness, CharSequence tag) {
synchronized (mLock) {
if (!BrightnessSynchronizer.floatEquals(mScreenBrightnessOverrideFromWindowManager,
brightness)) {
mScreenBrightnessOverrideFromWindowManager = brightness;
+ mScreenBrightnessOverrideFromWmTag = tag;
mDirty |= DIRTY_SETTINGS;
updatePowerStateLocked();
}
@@ -4760,6 +4767,8 @@
pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
pw.println(" mScreenBrightnessOverrideFromWindowManager="
+ mScreenBrightnessOverrideFromWindowManager);
+ pw.println(" mScreenBrightnessOverrideFromWmTag="
+ + mScreenBrightnessOverrideFromWmTag);
pw.println(" mUserActivityTimeoutOverrideFromWindowManager="
+ mUserActivityTimeoutOverrideFromWindowManager);
pw.println(" mUserInactiveOverrideFromWindowManager="
@@ -7074,12 +7083,14 @@
@VisibleForTesting
final class LocalService extends PowerManagerInternal {
@Override
- public void setScreenBrightnessOverrideFromWindowManager(float screenBrightness) {
+ public void setScreenBrightnessOverrideFromWindowManager(
+ float screenBrightness, CharSequence tag) {
if (screenBrightness < PowerManager.BRIGHTNESS_MIN
|| screenBrightness > PowerManager.BRIGHTNESS_MAX) {
screenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ tag = null;
}
- setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness);
+ setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness, tag);
}
@Override
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
index ab22e3e..1003a81 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
@@ -126,7 +126,7 @@
long totalTime = 0;
double totalPower = 0;
for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) {
- long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType);
+ long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType) / 1000;
totalTime += timePerLevel;
totalPower += mAveragePowerPerSignalQuality[i] * timePerLevel;
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 0f13492..39954b8 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -307,7 +307,10 @@
}
}
if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- builder.addConsumedPowerForCustomComponent(powerComponentId, powerAllProcStates);
+ if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
+ builder.addConsumedPowerForCustomComponent(powerComponentId,
+ powerAllProcStates);
+ }
} else {
builder.addConsumedPower(powerComponentId, powerAllProcStates,
BatteryConsumer.POWER_MODEL_UNDEFINED);
@@ -319,7 +322,9 @@
batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
+ if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) {
+ allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps);
+ }
} else {
BatteryConsumer.Key key = allAppsScope.getKey(powerComponentId,
BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 516fc65..f31280e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4078,8 +4078,8 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- DestroyActivityItem.obtain(token, finishing));
+ final DestroyActivityItem item = new DestroyActivityItem(token, finishing);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (Exception e) {
// We can just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
@@ -9997,7 +9997,7 @@
preserveWindow, getActivityWindowInfo());
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
- lifecycleItem = ResumeActivityItem.obtain(token, isTransitionForward(),
+ lifecycleItem = new ResumeActivityItem(token, isTransitionForward(),
shouldSendCompatFakeFocus());
} else {
lifecycleItem = PauseActivityItem.obtain(token);
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index bc82271..c02501f 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -86,7 +86,7 @@
+ "activityRecord=%s", activity);
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
try {
activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e81b440..021caaf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -942,7 +942,7 @@
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
- lifecycleItem = ResumeActivityItem.obtain(r.token, isTransitionForward,
+ lifecycleItem = new ResumeActivityItem(r.token, isTransitionForward,
r.shouldSendCompatFakeFocus());
} else if (r.isVisibleRequested()) {
lifecycleItem = PauseActivityItem.obtain(r.token);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index f2ccbc4..bded98c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -183,6 +183,7 @@
private Object mLastWindowFreezeSource = null;
private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ private CharSequence mScreenBrightnessOverrideTag;
private long mUserActivityTimeout = -1;
private boolean mUpdateRotation = false;
// Only set while traversing the default display based on its content.
@@ -770,6 +771,7 @@
}
mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mScreenBrightnessOverrideTag = null;
mUserActivityTimeout = -1;
mObscureApplicationContentOnSecondaryDisplays = false;
mSustainedPerformanceModeCurrent = false;
@@ -881,11 +883,15 @@
final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN
|| mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX
? PowerManager.BRIGHTNESS_INVALID_FLOAT : mScreenBrightnessOverride;
+ CharSequence overrideTag = null;
+ if (brightnessOverride != PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+ overrideTag = mScreenBrightnessOverrideTag;
+ }
int brightnessFloatAsIntBits = Float.floatToIntBits(brightnessOverride);
// Post these on a handler such that we don't call into power manager service while
// holding the window manager lock to avoid lock contention with power manager lock.
mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightnessFloatAsIntBits,
- 0).sendToTarget();
+ 0, overrideTag).sendToTarget();
mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
}
@@ -1040,6 +1046,7 @@
if (!syswin && w.mAttrs.screenBrightness >= 0
&& Float.isNaN(mScreenBrightnessOverride)) {
mScreenBrightnessOverride = w.mAttrs.screenBrightness;
+ mScreenBrightnessOverrideTag = w.getWindowTag();
}
// This function assumes that the contents of the default display are processed first
@@ -1112,7 +1119,7 @@
switch (msg.what) {
case SET_SCREEN_BRIGHTNESS_OVERRIDE:
mWmService.mPowerManagerInternal.setScreenBrightnessOverrideFromWindowManager(
- Float.intBitsToFloat(msg.arg1));
+ Float.intBitsToFloat(msg.arg1), (CharSequence) msg.obj);
break;
case SET_USER_ACTIVITY_TIMEOUT:
mWmService.mPowerManagerInternal.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 29e82f7..0b8b51b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1635,7 +1635,7 @@
final int topProcessState = mAtmService.mTopProcessState;
next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
next.abortAndClearOptionsAnimation();
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
next.token, topProcessState, dc.isNextTransitionForward(),
next.shouldSendCompatFakeFocus());
mAtmService.getLifecycleManager().scheduleTransactionItem(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index 990c383..04b79b4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertEquals;
-
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -43,17 +42,34 @@
assertEquals(mBrightnessReason.getReason(), BrightnessReason.REASON_UNKNOWN);
assertEquals(mBrightnessReason.getModifier(), 0);
+ CharSequence tag = "my tag";
mBrightnessReason.set(
getReason(BrightnessReason.REASON_BOOST, BrightnessReason.MODIFIER_THROTTLED));
+ mBrightnessReason.setTag(tag);
+
assertEquals(mBrightnessReason.getReason(), BrightnessReason.REASON_BOOST);
assertEquals(mBrightnessReason.getModifier(), BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(mBrightnessReason.getTag().toString(), tag);
}
@Test
- public void toStringGeneratesExpectedString() {
- String actualString = mBrightnessReason.toString();
- String expectedString = "doze [ low_pwr ]";
- assertEquals(actualString, expectedString);
+ public void toStringGeneratedExpectedString() {
+ assertEquals("doze [ low_pwr ]", mBrightnessReason.toString());
+ }
+
+ @Test
+ public void overrideTagString() {
+ // Should not print out the tag for "doze"
+ mBrightnessReason.setTag("my/tag");
+ assertEquals("doze(my/tag) [ low_pwr ]", mBrightnessReason.toString());
+
+ // Should print out tag for "override"
+ mBrightnessReason.setReason(BrightnessReason.REASON_OVERRIDE);
+ assertEquals("override(my/tag) [ low_pwr ]", mBrightnessReason.toString());
+
+ // Should not print anything if no tag.
+ mBrightnessReason.setTag(null);
+ assertEquals("override [ low_pwr ]", mBrightnessReason.toString());
}
@Test
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 94b8d68..39def75 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -256,7 +256,9 @@
.setBrightnessFactor(brightnessFactor)
.build();
+ CharSequence tag = "my/tag";
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ tag,
/* useProximitySensor= */ false,
/* boostScreenBrightness= */ false,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -274,6 +276,7 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
+ assertThat(displayPowerRequest.screenBrightnessOverrideTag.toString()).isEqualTo(tag);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -297,6 +300,7 @@
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -336,6 +340,7 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -374,6 +379,7 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -412,6 +418,7 @@
mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -451,6 +458,7 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -488,6 +496,7 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -526,6 +535,7 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -563,6 +573,7 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ null,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
index 7aa208b..5de323b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
@@ -207,4 +207,28 @@
assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
.isEqualTo("Mon,Wed,Fri-Sat,10:00 AM-4:00 PM");
}
+
+ @Test
+ public void getShortDaysSummary_onlyDays() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+
+ assertThat(SystemZenRules.getShortDaysSummary(mContext, scheduleInfo))
+ .isEqualTo("Mon-Fri");
+ }
+
+ @Test
+ public void getTimeSummary_onlyTime() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 11;
+ scheduleInfo.endHour = 15;
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+
+ assertThat(SystemZenRules.getTimeSummary(mContext, scheduleInfo))
+ .isEqualTo("11:00 AM-3:00 PM");
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 6ad1044..68ccb31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -193,7 +193,7 @@
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index eaa1641..c43f414 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -309,7 +309,7 @@
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
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 e9fcc40..12bc3ed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -615,7 +615,7 @@
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 3722fef..c0e90f9 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -35,6 +35,7 @@
import android.Manifest;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
@@ -77,6 +78,7 @@
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -413,6 +415,311 @@
verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
}
+ @Test
+ @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+ public void testCrashLoopWithRescuePartyAndRollbackObserver() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+ RollbackPackageHealthObserver rollbackObserver =
+ setUpRollbackPackageHealthObserver(watchdog);
+ VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
+
+ when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+ ApplicationInfo info = new ApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+ return info;
+ });
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: SCOPED_DEVICE_CONFIG_RESET
+ verify(rescuePartyObserver).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rollbackObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: ALL_DEVICE_CONFIG_RESET
+ verify(rescuePartyObserver).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+ verify(rollbackObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: WARM_REBOOT
+ verify(rescuePartyObserver).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+ verify(rollbackObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: Low impact rollback
+ verify(rollbackObserver).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+
+ // update available rollbacks to mock rollbacks being applied after the call to
+ // rollbackObserver.execute
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+ verify(rollbackObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+ public void testCrashLoopWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
+ throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+ RollbackPackageHealthObserver rollbackObserver =
+ setUpRollbackPackageHealthObserver(watchdog);
+ VersionedPackage versionedPackageA = new VersionedPackage(APP_A, VERSION_CODE);
+
+ when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+ ApplicationInfo info = new ApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+ return info;
+ });
+
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: WARM_REBOOT
+ verify(rescuePartyObserver).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rollbackObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: Low impact rollback
+ verify(rollbackObserver).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+ // update available rollbacks to mock rollbacks being applied after the call to
+ // rollbackObserver.execute
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
+ verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rollbackObserver, never()).execute(versionedPackageA,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+ public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserver() throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+ RollbackPackageHealthObserver rollbackObserver =
+ setUpRollbackPackageHealthObserver(watchdog);
+ String systemUi = "com.android.systemui";
+ VersionedPackage versionedPackageUi = new VersionedPackage(
+ systemUi, VERSION_CODE);
+ RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+ ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
+
+ when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+ ApplicationInfo info = new ApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+ return info;
+ });
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: SCOPED_DEVICE_CONFIG_RESET
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: ALL_DEVICE_CONFIG_RESET
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: WARM_REBOOT
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: Low impact rollback
+ verify(rollbackObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+ // update available rollbacks to mock rollbacks being applied after the call to
+ // rollbackObserver.execute
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 8);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
+ public void testCrashLoopSystemUIWithRescuePartyAndRollbackObserverEnableDeprecateFlagReset()
+ throws Exception {
+ PackageWatchdog watchdog = createWatchdog();
+ RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
+ RollbackPackageHealthObserver rollbackObserver =
+ setUpRollbackPackageHealthObserver(watchdog);
+ String systemUi = "com.android.systemui";
+ VersionedPackage versionedPackageUi = new VersionedPackage(
+ systemUi, VERSION_CODE);
+ RollbackInfo rollbackInfoUi = getRollbackInfo(systemUi, VERSION_CODE, 1,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
+ ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL, rollbackInfoUi));
+
+ when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).then(inv -> {
+ ApplicationInfo info = new ApplicationInfo();
+ info.flags |= ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+ return info;
+ });
+
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: WARM_REBOOT
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: Low impact rollback
+ verify(rollbackObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+
+ // update available rollbacks to mock rollbacks being applied after the call to
+ // rollbackObserver.execute
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
+
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+ // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
+ verify(rescuePartyObserver).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
+ verify(rollbackObserver, never()).execute(versionedPackageUi,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
+ }
+
RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
RollbackPackageHealthObserver rollbackObserver =
spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager));
@@ -424,7 +731,6 @@
watchdog.registerHealthObserver(rollbackObserver);
return rollbackObserver;
}
-
RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
setCrashRecoveryPropRescueBootCount(0);
RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
@@ -686,4 +992,20 @@
mTestLooper.moveTimeForward(milliSeconds);
mTestLooper.dispatchAll();
}
+
+ private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+ List<VersionedPackage> packages, int failureReason) {
+ long triggerFailureCount = watchdog.getTriggerFailureCount();
+ if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+ || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ triggerFailureCount = 1;
+ }
+ for (int i = 0; i < triggerFailureCount; i++) {
+ watchdog.onPackageFailure(packages, failureReason);
+ }
+ mTestLooper.dispatchAll();
+ if (Flags.recoverabilityDetection()) {
+ moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
+ }
+ }
}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 4c81939..3f9016b 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -55,7 +55,10 @@
cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
},
},
- header_libs: ["jni_headers"],
+ header_libs: [
+ "jni_headers",
+ "native_headers",
+ ],
static_libs: [
"libandroidfw",
"libutils",