Merge "Remove unneeded getUserOrDefault" into main
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index c1c96ea..014e4660 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -285,6 +285,14 @@
public static final String COMMAND_UNFREEZE = "android.wallpaper.unfreeze";
/**
+ * Command for {@link #sendWallpaperCommand}: in sendWallpaperCommand put extra to this command
+ * to give the bounds of space between the bottom of notifications and the top of shortcuts
+ * @hide
+ */
+ public static final String COMMAND_LOCKSCREEN_LAYOUT_CHANGED =
+ "android.wallpaper.lockscreen_layout_changed";
+
+ /**
* Extra passed back from setWallpaper() giving the new wallpaper's assigned ID.
* @hide
*/
diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
index b68bafe..534f461 100644
--- a/core/java/android/app/wearable/flags.aconfig
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -38,4 +38,12 @@
namespace: "machine_learning"
description: "This flag enables the APIs related to hotword in WearableSensingManager and WearableSensingService."
bug: "310055381"
+}
+
+flag {
+ name: "enable_concurrent_wearable_connections"
+ is_exported: true
+ namespace: "machine_learning"
+ description: "This flag enables the APIs for providing multiple concurrent connections to the WearableSensingService."
+ bug: "358133158"
}
\ No newline at end of file
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index dfc591b..8ef62e3 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -62,7 +62,7 @@
* sleep (CPU off, display dark, device waiting for external input),
* but is not affected by clock scaling, idle, or other power saving
* mechanisms. This is the basis for most interval timing
- * such as {@link Thread#sleep(long) Thread.sleep(millls)},
+ * such as {@link Thread#sleep(long) Thread.sleep(millis)},
* {@link Object#wait(long) Object.wait(millis)}, and
* {@link System#nanoTime System.nanoTime()}. This clock is guaranteed
* to be monotonic, and is suitable for interval timing when the
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 06820cd..d7b5211 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -46,6 +46,7 @@
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
+import android.appwidget.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -4119,6 +4120,71 @@
public void visitUris(@NonNull Consumer<Uri> visitor) {
mNestedViews.visitUris(visitor);
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ if (!Flags.remoteViewsProto()) return;
+ final long token = out.start(RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION);
+ out.write(RemoteViewsProto.ViewGroupAddAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.ViewGroupAddAction.INDEX, mIndex);
+ out.write(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, mStableId);
+ long rvToken = out.start(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS);
+ mNestedViews.writePreviewToProto(context, out);
+ out.end(rvToken);
+ out.end(token);
+ }
+ }
+
+ private PendingResources<Action> createViewGroupActionAddFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ViewGroupAddAction.VIEW_ID:
+ values.put(RemoteViewsProto.ViewGroupAddAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ViewGroupAddAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS:
+ final long nvToken = in.start(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS);
+ values.put(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS,
+ createFromProto(in));
+ in.end(nvToken);
+ break;
+ case (int) RemoteViewsProto.ViewGroupAddAction.INDEX:
+ values.put(RemoteViewsProto.ViewGroupAddAction.INDEX,
+ in.readInt(RemoteViewsProto.ViewGroupAddAction.INDEX));
+ break;
+ case (int) RemoteViewsProto.ViewGroupAddAction.STABLE_ID:
+ values.put(RemoteViewsProto.ViewGroupAddAction.STABLE_ID,
+ in.readInt(RemoteViewsProto.ViewGroupAddAction.STABLE_ID));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ViewGroupAddAction.VIEW_ID,
+ RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewGroupAddAction.VIEW_ID);
+ return new ViewGroupActionAdd(viewId, ((PendingResources<RemoteViews>) values.get(
+ RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS)).create(context, resources,
+ rootData, depth),
+ (int) values.get(RemoteViewsProto.ViewGroupAddAction.INDEX, 0),
+ (int) values.get(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, 0));
+ };
}
/**
@@ -4241,6 +4307,60 @@
public int mergeBehavior() {
return MERGE_APPEND;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION);
+ out.write(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ if (mViewIdToKeep != REMOVE_ALL_VIEWS_ID) {
+ out.write(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP,
+ appResources.getResourceName(mViewIdToKeep));
+ }
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID:
+ values.put(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP:
+ values.put(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP,
+ in.readString(
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID);
+ int viewIdToKeep = (values.indexOfKey(
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP) >= 0)
+ ? getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP)
+ : REMOVE_ALL_VIEWS_ID;
+ return new ViewGroupActionRemove(viewId, viewIdToKeep);
+ };
+ }
}
/**
@@ -4497,6 +4617,200 @@
visitIconUri(mI4, visitor);
}
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context,
+ Resources appResources) { // rebase
+ final long token = out.start(RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION);
+ out.write(RemoteViewsProto.TextViewDrawableAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, mIsRelative);
+ if (mUseIcons) {
+ long iconsToken = out.start(RemoteViewsProto.TextViewDrawableAction.ICONS);
+ if (mI1 != null) {
+ writeIconToProto(out, appResources, mI1,
+ RemoteViewsProto.TextViewDrawableAction.Icons.ONE);
+ }
+ if (mI2 != null) {
+ writeIconToProto(out, appResources, mI2,
+ RemoteViewsProto.TextViewDrawableAction.Icons.TWO);
+ }
+ if (mI3 != null) {
+ writeIconToProto(out, appResources, mI3,
+ RemoteViewsProto.TextViewDrawableAction.Icons.THREE);
+ }
+ if (mI4 != null) {
+ writeIconToProto(out, appResources, mI4,
+ RemoteViewsProto.TextViewDrawableAction.Icons.FOUR);
+ }
+ out.end(iconsToken);
+ } else {
+ long resourcesToken = out.start(RemoteViewsProto.TextViewDrawableAction.RESOURCES);
+ if (mD1 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.ONE,
+ appResources.getResourceName(mD1));
+ }
+ if (mD2 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.TWO,
+ appResources.getResourceName(mD2));
+ }
+ if (mD3 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.THREE,
+ appResources.getResourceName(mD3));
+ }
+ if (mD4 != 0) {
+ out.write(RemoteViewsProto.TextViewDrawableAction.Resources.FOUR,
+ appResources.getResourceName(mD4));
+ }
+ out.end(resourcesToken);
+ }
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ values.put(RemoteViewsProto.TextViewDrawableAction.ICONS,
+ new SparseArray<PendingResources<Icon>>());
+ values.put(RemoteViewsProto.TextViewDrawableAction.RESOURCES,
+ new SparseArray<String>());
+ final long token = in.start(RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewDrawableAction.VIEW_ID:
+ values.put(RemoteViewsProto.TextViewDrawableAction.VIEW_ID,
+ in.readString(RemoteViewsProto.TextViewDrawableAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE:
+ values.put(RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE,
+ in.readBoolean(
+ RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.RESOURCES:
+ final long resourcesToken = in.start(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.ONE:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 1, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction.Resources.ONE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.TWO:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 2, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction.Resources.TWO));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.THREE:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 3, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction
+ .Resources.THREE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Resources.FOUR:
+ ((SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put(
+ 4, in.readString(
+ RemoteViewsProto
+ .TextViewDrawableAction
+ .Resources.FOUR));
+ break;
+ default:
+ Log.w(LOG_TAG,
+ "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(resourcesToken);
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.ICONS:
+ final long iconsToken = in.start(
+ RemoteViewsProto.TextViewDrawableAction.ICONS);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.ONE:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(1,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.ONE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.TWO:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(2,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.TWO));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.THREE:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(3,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.THREE));
+ break;
+ case (int) RemoteViewsProto.TextViewDrawableAction.Icons.FOUR:
+ ((SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS)).put(4,
+ createIconFromProto(in,
+ RemoteViewsProto
+ .TextViewDrawableAction.Icons.FOUR));
+ break;
+ default:
+ Log.w(LOG_TAG,
+ "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(iconsToken);
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.TextViewDrawableAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.TextViewDrawableAction.VIEW_ID);
+ SparseArray<PendingResources<Icon>> icons =
+ (SparseArray<PendingResources<Icon>>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.ICONS);
+ SparseArray<String> resArray = (SparseArray<String>) values.get(
+ RemoteViewsProto.TextViewDrawableAction.RESOURCES);
+ boolean isRelative = (boolean) values.get(
+ RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, false);
+ if (icons.size() > 0) {
+ return new TextViewDrawableAction(viewId, isRelative,
+ icons.get(1).create(context, resources, rootData, depth),
+ icons.get(2).create(context, resources, rootData, depth),
+ icons.get(3).create(context, resources, rootData, depth),
+ icons.get(4).create(context, resources, rootData, depth));
+ } else {
+ int first = resArray.contains(1) ? getAsIdentifier(resources, resArray, 1) : 0;
+ int second = resArray.contains(2) ? getAsIdentifier(resources, resArray, 2) : 0;
+ int third = resArray.contains(3) ? getAsIdentifier(resources, resArray, 3) : 0;
+ int fourth = resArray.contains(4) ? getAsIdentifier(resources, resArray, 4) : 0;
+ return new TextViewDrawableAction(viewId, isRelative, first, second, third,
+ fourth);
+ }
+ };
+ }
}
/**
@@ -4535,6 +4849,58 @@
public int getActionTag() {
return TEXT_VIEW_SIZE_ACTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION);
+ out.write(RemoteViewsProto.TextViewSizeAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.TextViewSizeAction.UNITS, mUnits);
+ out.write(RemoteViewsProto.TextViewSizeAction.SIZE, mSize);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.TextViewSizeAction.VIEW_ID:
+ values.put(RemoteViewsProto.TextViewSizeAction.VIEW_ID,
+ in.readString(RemoteViewsProto.TextViewSizeAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.TextViewSizeAction.UNITS:
+ values.put(RemoteViewsProto.TextViewSizeAction.UNITS,
+ in.readInt(RemoteViewsProto.TextViewSizeAction.UNITS));
+ break;
+ case (int) RemoteViewsProto.TextViewSizeAction.SIZE:
+ values.put(RemoteViewsProto.TextViewSizeAction.SIZE,
+ in.readFloat(RemoteViewsProto.TextViewSizeAction.SIZE));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.TextViewSizeAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.TextViewSizeAction.VIEW_ID);
+ return new TextViewSizeAction(viewId,
+ (int) values.get(RemoteViewsProto.TextViewSizeAction.UNITS, 0),
+ (float) values.get(RemoteViewsProto.TextViewSizeAction.SIZE, 0));
+ };
+ }
}
/**
@@ -4579,6 +4945,70 @@
public int getActionTag() {
return VIEW_PADDING_ACTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(RemoteViewsProto.Action.VIEW_PADDING_ACTION);
+ out.write(RemoteViewsProto.ViewPaddingAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.ViewPaddingAction.LEFT, mLeft);
+ out.write(RemoteViewsProto.ViewPaddingAction.RIGHT, mRight);
+ out.write(RemoteViewsProto.ViewPaddingAction.TOP, mTop);
+ out.write(RemoteViewsProto.ViewPaddingAction.BOTTOM, mBottom);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(RemoteViewsProto.Action.VIEW_PADDING_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.ViewPaddingAction.VIEW_ID:
+ values.put(RemoteViewsProto.ViewPaddingAction.VIEW_ID,
+ in.readString(RemoteViewsProto.ViewPaddingAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.LEFT:
+ values.put(RemoteViewsProto.ViewPaddingAction.LEFT,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.LEFT));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.RIGHT:
+ values.put(RemoteViewsProto.ViewPaddingAction.RIGHT,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.RIGHT));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.TOP:
+ values.put(RemoteViewsProto.ViewPaddingAction.TOP,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.TOP));
+ break;
+ case (int) RemoteViewsProto.ViewPaddingAction.BOTTOM:
+ values.put(RemoteViewsProto.ViewPaddingAction.BOTTOM,
+ in.readInt(RemoteViewsProto.ViewPaddingAction.BOTTOM));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values, new long[]{RemoteViewsProto.ViewPaddingAction.VIEW_ID});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.ViewPaddingAction.VIEW_ID);
+ return new ViewPaddingAction(viewId,
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.LEFT, 0),
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.TOP, 0),
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.RIGHT, 0),
+ (int) values.get(RemoteViewsProto.ViewPaddingAction.BOTTOM, 0));
+ };
+ }
}
/**
@@ -5241,6 +5671,69 @@
public int getActionTag() {
return SET_VIEW_OUTLINE_RADIUS_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return true;
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ final long token = out.start(
+ RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION);
+ out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID,
+ appResources.getResourceName(mViewId));
+ out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE, mValueType);
+ out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE, mValue);
+ out.end(token);
+ }
+
+ public static PendingResources<Action> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+
+ final long token = in.start(
+ RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID:
+ values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID,
+ in.readString(
+ RemoteViewsProto
+ .SetViewOutlinePreferredRadiusAction.VIEW_ID));
+ break;
+ case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE:
+ values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE,
+ in.readInt(
+ RemoteViewsProto
+ .SetViewOutlinePreferredRadiusAction.VALUE_TYPE));
+ break;
+ case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE:
+ values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE,
+ in.readInt(
+ RemoteViewsProto
+ .SetViewOutlinePreferredRadiusAction.VALUE));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID,
+ RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE});
+
+ return (context, resources, rootData, depth) -> {
+ int viewId = getAsIdentifier(resources, values,
+ RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID);
+ return new SetViewOutlinePreferredRadiusAction(viewId,
+ (int) values.get(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE,
+ 0), (int) values.get(
+ RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE));
+ };
+ }
}
/**
@@ -5324,6 +5817,46 @@
public int getActionTag() {
return SET_DRAW_INSTRUCTION_TAG;
}
+
+ @Override
+ public boolean canWriteToProto() {
+ return drawDataParcel();
+ }
+
+ @Override
+ public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) {
+ if (!drawDataParcel()) return;
+ final long token = out.start(RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION);
+ if (mInstructions != null) {
+ for (byte[] bytes : mInstructions.mInstructions) {
+ out.write(RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS, bytes);
+ }
+ }
+ out.end(token);
+ }
+ }
+
+ @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+ private PendingResources<Action> createSetDrawInstructionActionFromProto(ProtoInputStream in)
+ throws Exception {
+ List<byte[]> instructions = new ArrayList<byte[]>();
+
+ final long token = in.start(RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS:
+ instructions.add(
+ in.readBytes(RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ return (context, resources, rootData, depth) -> new SetDrawInstructionAction(
+ new DrawInstructions.Builder(instructions).build());
}
/**
@@ -9604,12 +10137,20 @@
}
if (ref.mMode == MODE_NORMAL) {
rv.setIdealSize(ref.mIdealSize);
+ boolean hasDrawInstructionAction = false;
for (PendingResources<Action> pendingAction : ref.mActions) {
Action action = pendingAction.create(appContext, appResources, rootData, depth);
if (action != null) {
+ if (action instanceof SetDrawInstructionAction) {
+ hasDrawInstructionAction = true;
+ }
rv.addAction(action);
}
}
+ if (rv.mHasDrawInstructions && !hasDrawInstructionAction) {
+ throw new InvalidProtoException(
+ "RemoteViews proto is missing DrawInstructions");
+ }
return rv;
} else if (ref.mMode == MODE_HAS_SIZED_REMOTEVIEWS) {
List<RemoteViews> sizedViews = new ArrayList<>();
@@ -9685,6 +10226,23 @@
return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in);
case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION:
return SetRippleDrawableColor.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION:
+ return SetViewOutlinePreferredRadiusAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION:
+ return TextViewDrawableAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION:
+ return TextViewSizeAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION:
+ return rv.createViewGroupActionAddFromProto(in);
+ case (int) RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION:
+ return ViewGroupActionRemove.createFromProto(in);
+ case (int) RemoteViewsProto.Action.VIEW_PADDING_ACTION:
+ return ViewPaddingAction.createFromProto(in);
+ case (int) RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION:
+ if (!drawDataParcel()) {
+ return null;
+ }
+ return rv.createSetDrawInstructionActionFromProto(in);
default:
throw new RuntimeException("Unhandled field while reading Action proto!\n"
+ ProtoUtils.currentFieldToString(in));
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index f477d32..6a987a4 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -32,6 +32,8 @@
*
* Do not change the tag number or type of any fields in order to maintain compatibility with
* previous versions. If a field is deleted, use `reserved` to mark its tag number.
+ *
+ * Next tag: 17
*/
message RemoteViewsProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -290,6 +292,7 @@
}
}
+ // Next tag: 23
message Action {
oneof action {
AttributeReflectionAction attribute_reflection_action = 1;
@@ -307,6 +310,13 @@
SetRadioGroupCheckedAction set_radio_group_checked_action = 13;
SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14;
SetRippleDrawableColorAction set_ripple_drawable_color_action = 15;
+ SetViewOutlinePreferredRadiusAction set_view_outline_preferred_radius_action = 16;
+ TextViewDrawableAction text_view_drawable_action = 17;
+ TextViewSizeAction text_view_size_action = 18;
+ ViewGroupAddAction view_group_add_action = 19;
+ ViewGroupRemoveAction view_group_remove_action = 20;
+ ViewPaddingAction view_padding_action = 21;
+ SetDrawInstructionAction set_draw_instruction_action = 22;
}
}
@@ -428,6 +438,65 @@
optional string view_id = 1;
optional android.content.res.ColorStateListProto color_state_list = 2;
}
+
+ message SetViewOutlinePreferredRadiusAction {
+ optional string view_id = 1;
+ optional int32 value_type = 2;
+ optional int32 value = 3;
+ }
+
+ message TextViewDrawableAction {
+ optional string view_id = 1;
+ optional bool is_relative = 2;
+ oneof drawables {
+ Resources resources = 3;
+ Icons icons = 4;
+ };
+
+ message Resources {
+ optional string one = 1;
+ optional string two = 2;
+ optional string three = 3;
+ optional string four = 4;
+ }
+
+ message Icons {
+ optional Icon one = 1;
+ optional Icon two = 2;
+ optional Icon three = 3;
+ optional Icon four = 4;
+ }
+ }
+
+ message TextViewSizeAction {
+ optional string view_id = 1;
+ optional int32 units = 2;
+ optional float size = 3;
+ }
+
+ message ViewGroupAddAction {
+ optional string view_id = 1;
+ optional RemoteViewsProto nested_views = 2;
+ optional int32 index = 3;
+ optional int32 stableId = 4;
+ }
+
+ message ViewGroupRemoveAction {
+ optional string view_id = 1;
+ optional string view_id_to_keep = 2;
+ }
+
+ message ViewPaddingAction {
+ optional string view_id = 1;
+ optional int32 left = 2;
+ optional int32 right = 3;
+ optional int32 top = 4;
+ optional int32 bottom = 5;
+ }
+
+ message SetDrawInstructionAction {
+ repeated bytes instructions = 1;
+ }
}
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
index e81cbfb..c0ef4b14 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -29,37 +29,6 @@
namespace uirenderer {
namespace skiapipeline {
-BackdropFilterDrawable::~BackdropFilterDrawable() {}
-
-bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties,
- int backdropImageWidth, int backdropImageHeight) {
- // the drawing bounds for blurred content.
- mDstBounds.setWH(properties.getWidth(), properties.getHeight());
-
- float alphaMultiplier = 1.0f;
- RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true);
-
- // get proper subset for previous content.
- canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds);
- SkRect imageSubset(mImageSubset);
- // ensure the subset is inside bounds of previous content.
- if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) {
- return false;
- }
-
- // correct the drawing bounds if subset was changed.
- if (mImageSubset != imageSubset) {
- SkMatrix inverse;
- if (canvas->getTotalMatrix().invert(&inverse)) {
- inverse.mapRect(&mDstBounds, mImageSubset);
- }
- }
-
- // follow the alpha from the target RenderNode.
- mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier);
- return true;
-}
-
void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
const RenderProperties& properties = mTargetRenderNode->properties();
auto* backdropFilter = properties.layerProperties().getBackdropImageFilter();
@@ -68,27 +37,43 @@
return;
}
- auto backdropImage = surface->makeImageSnapshot();
- // sync necessary properties from target RenderNode.
- if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {
+ SkRect srcBounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+
+ float alphaMultiplier = 1.0f;
+ RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true);
+ SkPaint paint;
+ paint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier);
+
+ SkRect surfaceSubset;
+ canvas->getTotalMatrix().mapRect(&surfaceSubset, srcBounds);
+ if (!surfaceSubset.intersect(SkRect::MakeWH(surface->width(), surface->height()))) {
return;
}
- auto imageSubset = mImageSubset.roundOut();
+ auto backdropImage = surface->makeImageSnapshot(surfaceSubset.roundOut());
+
+ SkIRect imageBounds = SkIRect::MakeWH(backdropImage->width(), backdropImage->height());
+ SkIPoint offset;
+ SkIRect imageSubset;
+
#ifdef __ANDROID__
if (canvas->recordingContext()) {
backdropImage =
SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter,
- imageSubset, imageSubset, &mOutSubset, &mOutOffset);
+ imageBounds, imageBounds, &imageSubset, &offset);
} else
#endif
{
- backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset,
- imageSubset, &mOutSubset, &mOutOffset);
+ backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageBounds,
+ imageBounds, &imageSubset, &offset);
}
- canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
- SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
- SkCanvas::kStrict_SrcRectConstraint);
+
+ canvas->save();
+ canvas->resetMatrix();
+ canvas->drawImageRect(backdropImage, SkRect::Make(imageSubset), surfaceSubset,
+ SkSamplingOptions(SkFilterMode::kLinear), &paint,
+ SkCanvas::kFast_SrcRectConstraint);
+ canvas->restore();
}
} // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
index 9e35837..5e216a1 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
@@ -37,23 +37,10 @@
BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas)
: mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {}
- ~BackdropFilterDrawable();
+ ~BackdropFilterDrawable() = default;
private:
RenderNode* mTargetRenderNode;
- SkPaint mPaint;
-
- SkRect mDstBounds;
- SkRect mImageSubset;
- SkIRect mOutSubset;
- SkIPoint mOutOffset;
-
- /**
- * Check all necessary properties before actual drawing.
- * Return true if ready to draw.
- */
- bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth,
- int backdropImageHeight);
protected:
void onDraw(SkCanvas* canvas) override;
diff --git a/libs/hwui/tests/common/scenes/BackdropBlur.cpp b/libs/hwui/tests/common/scenes/BackdropBlur.cpp
new file mode 100644
index 0000000..a1133ff
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/BackdropBlur.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include <SkBlendMode.h>
+
+#include "SkImageFilter.h"
+#include "SkImageFilters.h"
+#include "TestSceneBase.h"
+#include "utils/Blur.h"
+
+class BackdropBlurAnimation : public TestScene {
+private:
+ std::unique_ptr<TestScene> listView;
+
+public:
+ explicit BackdropBlurAnimation(const TestScene::Options& opts) {
+ listView.reset(TestScene::testMap()["listview"].createScene(opts));
+ }
+
+ void createContent(int width, int height, Canvas& canvas) override {
+ sp<RenderNode> list = TestUtils::createNode(
+ 0, 0, width, height,
+ [this, width, height](RenderProperties& props, Canvas& canvas) {
+ props.setClipToBounds(false);
+ listView->createContent(width, height, canvas);
+ });
+
+ canvas.drawRenderNode(list.get());
+
+ int x = width / 8;
+ int y = height / 4;
+ sp<RenderNode> blurNode = TestUtils::createNode(
+ x, y, width - x, height - y, [](RenderProperties& props, Canvas& canvas) {
+ props.mutableOutline().setRoundRect(0, 0, props.getWidth(), props.getHeight(),
+ dp(16), 1);
+ props.mutableOutline().setShouldClip(true);
+ sk_sp<SkImageFilter> blurFilter = SkImageFilters::Blur(
+ Blur::convertRadiusToSigma(dp(8)), Blur::convertRadiusToSigma(dp(8)),
+ SkTileMode::kClamp, nullptr, nullptr);
+ props.mutateLayerProperties().setBackdropImageFilter(blurFilter.get());
+ canvas.drawColor(0x33000000, SkBlendMode::kSrcOver);
+ });
+
+ canvas.drawRenderNode(blurNode.get());
+ }
+
+ void doFrame(int frameNr) override { listView->doFrame(frameNr); }
+};
+
+static TestScene::Registrar _BackdropBlur(TestScene::Info{
+ "backdropblur", "A rounded rect that does a blur-behind of a sky animation.",
+ [](const TestScene::Options& opts) -> test::TestScene* {
+ return new BackdropBlurAnimation(opts);
+ }});
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index ca54087..4b29100 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -1280,7 +1280,7 @@
canvas->drawDrawable(&backdropDrawable);
// the drawable is still visible, ok to draw.
EXPECT_EQ(2, canvas->mDrawCounter);
- EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds);
+ EXPECT_EQ(SkRect::MakeLTRB(30, 30, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds);
canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT);
canvas->drawDrawable(&drawable);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 26e6e71..a21a805 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1473,3 +1473,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "magic_portrait_wallpapers"
+ namespace: "systemui"
+ description: "Magic Portrait related changes in systemui"
+ bug: "370863642"
+}
+
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt b/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt
deleted file mode 100644
index 97c8076..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.activity
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.contentColorFor
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.rememberSystemUiController
-import com.android.compose.theme.PlatformTheme
-
-/** Scaffolding for an edge-to-edge activity content. */
-@Composable
-fun EdgeToEdgeActivityContent(
- modifier: Modifier = Modifier,
- content: @Composable () -> Unit,
-) {
- // Make the status and navigation bars transparent, ensuring that the status bar icons are dark
- // when the theme is light and vice-versa.
- val systemUiController = rememberSystemUiController()
- val isDarkTheme = isSystemInDarkTheme()
- val useDarkIcons = !isDarkTheme
- DisposableEffect(systemUiController, useDarkIcons) {
- systemUiController.setSystemBarsColor(
- color = Color.Transparent,
- darkIcons = useDarkIcons,
- )
- onDispose {}
- }
-
- PlatformTheme(isDarkTheme) {
- val backgroundColor = MaterialTheme.colorScheme.background
- Box(modifier.fillMaxSize().background(backgroundColor)) {
- CompositionLocalProvider(LocalContentColor provides contentColorFor(backgroundColor)) {
- content()
- }
- }
- }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index bf8f6ce..7dc2901 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -522,13 +522,7 @@
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
- val state = remember {
- MutableSceneTransitionLayoutState(
- currentSceneKey,
- SceneTransitions,
- enableInterruptions = false,
- )
- }
+ val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentSceneKey) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 2e8fc14..2657d7c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -234,7 +234,6 @@
canHideOverlay: (OverlayKey) -> Boolean = { true },
canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
stateLinks: List<StateLink> = emptyList(),
- enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
@@ -245,7 +244,6 @@
canHideOverlay,
canReplaceOverlay,
stateLinks,
- enableInterruptions,
)
}
@@ -261,9 +259,6 @@
true
},
private val stateLinks: List<StateLink> = emptyList(),
-
- // TODO(b/290930950): Remove this flag.
- internal val enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
@@ -406,13 +401,6 @@
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
}
- if (!enableInterruptions) {
- // Set the current transition.
- check(transitionStates.size == 1)
- transitionStates = listOf(transition)
- return
- }
-
when (val currentState = transitionStates.last()) {
is TransitionState.Idle -> {
// Replace [Idle] by [transition].
@@ -755,9 +743,6 @@
private const val TAG = "SceneTransitionLayoutState"
-/** Whether support for interruptions in enabled by default. */
-internal const val DEFAULT_INTERRUPTIONS_ENABLED = true
-
/**
* The max number of concurrent transitions. If the number of transitions goes past this number,
* this probably means that there is a leak and we will Log.wtf before clearing the list of
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index d6751ae..9d3f25e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -337,10 +337,6 @@
}
internal open fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
- if (!layoutImpl.state.enableInterruptions) {
- return 0f
- }
-
if (replacedTransition != null) {
return replacedTransition.interruptionProgress(layoutImpl)
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 1eed54e..a2c2729 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -213,10 +213,6 @@
from(SceneA, to = SceneB) { spec = tween }
from(SceneB, to = SceneC) { spec = tween }
},
-
- // Disable interruptions so that the current transition is directly removed
- // when starting a new one.
- enableInterruptions = false,
)
}
@@ -243,7 +239,12 @@
onElement(TestElements.Bar).assertExists()
// Start transition from SceneB to SceneC
- rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
+ rule.runOnUiThread {
+ // We snap to scene B so that the transition A => B is removed from the list of
+ // transitions.
+ state.snapToScene(SceneB)
+ state.setTargetScene(SceneC, coroutineScope)
+ }
}
at(3 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 400f0b3..b00b894b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -85,15 +85,9 @@
/** The content under test. */
@Composable
- private fun TestContent(enableInterruptions: Boolean = true) {
+ private fun TestContent() {
coroutineScope = rememberCoroutineScope()
- layoutState = remember {
- MutableSceneTransitionLayoutState(
- SceneA,
- EmptyTestTransitions,
- enableInterruptions = enableInterruptions,
- )
- }
+ layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -205,7 +199,7 @@
@Test
fun testSharedElement() {
- rule.setContent { TestContent(enableInterruptions = false) }
+ rule.setContent { TestContent() }
// In scene A, the shared element SharedFoo() is at the top end of the layout and has a size
// of 50.dp.
@@ -253,6 +247,9 @@
.isWithin(DpOffsetSubject.DefaultTolerance)
.of(DpOffset(25.dp, 25.dp))
+ // Finish the transition.
+ rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+
// Animate to scene C, let the animation start then go to the middle of the transition.
currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index fe5024f..59676ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -17,10 +17,17 @@
package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
+import android.view.View
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of the wallpaper repository. */
class FakeWallpaperRepository : WallpaperRepository {
override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
+ override var rootView: View? = null
+ private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
+
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ _notificationStackAbsoluteBottom.value = bottom
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index ec52055..95d1b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -68,11 +68,16 @@
val previewClock: Flow<ClockController>
+ /** top of notifications without bcsmartspace in small clock settings */
+ val notificationDefaultTop: StateFlow<Float>
+
val clockEventController: ClockEventController
val shouldForceSmallClock: Boolean
fun setClockSize(size: ClockSize)
+
+ fun setNotificationDefaultTop(top: Float)
}
@SysUISingleton
@@ -108,7 +113,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = getClockSize()
+ initialValue = getClockSize(),
)
override val currentClockId: Flow<ClockId> =
@@ -138,7 +143,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = clockRegistry.createCurrentClock()
+ initialValue = clockRegistry.createCurrentClock(),
)
override val previewClock: Flow<ClockController> =
@@ -149,6 +154,14 @@
clockRegistry.createCurrentClock()
}
+ private val _notificationDefaultTop: MutableStateFlow<Float> = MutableStateFlow(0F)
+
+ override val notificationDefaultTop: StateFlow<Float> = _notificationDefaultTop.asStateFlow()
+
+ override fun setNotificationDefaultTop(top: Float) {
+ _notificationDefaultTop.value = top
+ }
+
override val shouldForceSmallClock: Boolean
get() =
featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
@@ -160,7 +173,7 @@
secureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
/* defaultValue= */ 1,
- UserHandle.USER_CURRENT
+ UserHandle.USER_CURRENT,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 130242f..8210174 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -269,6 +269,9 @@
*/
val isEncryptedOrLockdown: Flow<Boolean>
+ /** The top of shortcut in screen, used by wallpaper to find remaining space in lockscreen */
+ val shortcutAbsoluteTop: StateFlow<Float>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -339,6 +342,8 @@
* otherwise.
*/
fun isShowKeyguardWhenReenabled(): Boolean
+
+ fun setShortcutAbsoluteTop(top: Float)
}
/** Encapsulates application state for the keyguard. */
@@ -503,7 +508,7 @@
trySendWithFailureLogging(
statusBarStateController.dozeAmount,
TAG,
- "initial dozeAmount"
+ "initial dozeAmount",
)
awaitClose { statusBarStateController.removeCallback(callback) }
@@ -521,7 +526,7 @@
object : DozeTransitionCallback {
override fun onDozeTransition(
oldState: DozeMachine.State,
- newState: DozeMachine.State
+ newState: DozeMachine.State,
) {
trySendWithFailureLogging(
DozeTransitionModel(
@@ -529,7 +534,7 @@
to = dozeMachineStateToModel(newState),
),
TAG,
- "doze transition model"
+ "doze transition model",
)
}
}
@@ -541,7 +546,7 @@
to = dozeMachineStateToModel(dozeTransitionListener.newState),
),
TAG,
- "initial doze transition model"
+ "initial doze transition model",
)
awaitClose { dozeTransitionListener.removeCallback(callback) }
@@ -579,7 +584,7 @@
trySendWithFailureLogging(
statusBarStateIntToObject(state),
TAG,
- "state"
+ "state",
)
}
}
@@ -590,7 +595,7 @@
.stateIn(
scope,
SharingStarted.Eagerly,
- statusBarStateIntToObject(statusBarStateController.state)
+ statusBarStateIntToObject(statusBarStateController.state),
)
private val _biometricUnlockState: MutableStateFlow<BiometricUnlockModel> =
@@ -610,7 +615,7 @@
trySendWithFailureLogging(
authController.fingerprintSensorLocation,
TAG,
- "AuthController.Callback#onFingerprintLocationChanged"
+ "AuthController.Callback#onFingerprintLocationChanged",
)
}
@@ -635,6 +640,9 @@
private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
+ private val _shortcutAbsoluteTop = MutableStateFlow(0F)
+ override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
+
init {
val callback =
object : KeyguardStateController.Callback {
@@ -721,6 +729,10 @@
}
}
+ override fun setShortcutAbsoluteTop(top: Float) {
+ _shortcutAbsoluteTop.value = top
+ }
+
private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
return when (state) {
DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index c0049d4..5b7eedd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -94,7 +94,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = ClockSize.LARGE
+ initialValue = ClockSize.LARGE,
)
} else {
keyguardClockRepository.clockSize
@@ -152,4 +152,8 @@
clock.largeClock.animations.fold(foldFraction)
}
}
+
+ fun setNotificationStackDefaultTop(top: Float) {
+ keyguardClockRepository.setNotificationDefaultTop(top)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e6ee112..d7f96b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -526,6 +526,10 @@
repository.showDismissibleKeyguard()
}
+ fun setShortcutAbsoluteTop(top: Float) {
+ repository.setShortcutAbsoluteTop(top)
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index a1c963b..0b10c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -44,6 +44,7 @@
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shared.R as sharedR
import com.android.systemui.util.ui.value
import dagger.Lazy
@@ -70,6 +71,7 @@
val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
private val rootViewModel: KeyguardRootViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
+ private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
) : KeyguardSection() {
private var disposableHandle: DisposableHandle? = null
@@ -172,7 +174,7 @@
}
}
- open fun applyDefaultConstraints(constraints: ConstraintSet) {
+ fun applyDefaultConstraints(constraints: ConstraintSet) {
val guideline =
if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
else R.id.split_shade_guideline
@@ -211,6 +213,28 @@
// Explicitly clear pivot to force recalculate pivot instead of using legacy value
setTransformPivot(R.id.lockscreen_clock_view_large, Float.NaN, Float.NaN)
+
+ val smallClockBottom =
+ keyguardClockViewModel.getSmallClockTopMargin() +
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_height
+ )
+ val dateWeatherSmartspaceHeight = getDimen(context, DATE_WEATHER_VIEW_HEIGHT).toFloat()
+ val marginBetweenSmartspaceAndNotification =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_status_view_bottom_margin
+ ) +
+ if (context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)) {
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+ } else {
+ 0
+ }
+
+ clockInteractor.setNotificationStackDefaultTop(
+ smallClockBottom +
+ dateWeatherSmartspaceHeight +
+ marginBetweenSmartspaceAndNotification
+ )
}
constrainWeatherClockDateIconsBarrier(constraints)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 6c6e14c..d3895de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
@@ -52,6 +53,7 @@
private val indicationController: KeyguardIndicationController,
private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
+ private val keyguardInteractor: KeyguardInteractor,
) : BaseShortcutSection() {
// Amount to increase the bottom margin by to avoid colliding with inset
@@ -117,7 +119,7 @@
BOTTOM,
PARENT_ID,
BOTTOM,
- verticalOffsetMargin + safeInsetBottom
+ verticalOffsetMargin + safeInsetBottom,
)
constrainWidth(R.id.end_button, width)
@@ -128,7 +130,7 @@
BOTTOM,
PARENT_ID,
BOTTOM,
- verticalOffsetMargin + safeInsetBottom
+ verticalOffsetMargin + safeInsetBottom,
)
// The constraint set visibility for start and end button are default visible, set to
@@ -136,5 +138,13 @@
setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
}
+
+ val shortcutAbsoluteTopInScreen =
+ (resources.displayMetrics.heightPixels -
+ (verticalOffsetMargin + safeInsetBottom) -
+ height)
+ .toFloat()
+
+ keyguardInteractor.setShortcutAbsoluteTop(shortcutAbsoluteTopInScreen)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 2c6b09c..adb3352 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3884,6 +3884,7 @@
}
showingLayout.dump(pw, args);
dumpCustomOutline(pw, args);
+ dumpClipping(pw, args);
if (getViewState() != null) {
getViewState().dump(pw, args);
pw.println();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index afda426..ef6cad1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -856,17 +856,23 @@
pw.println();
}
if (DUMP_VERBOSE) {
- pw.println("mInRemovalAnimation: " + mInRemovalAnimation);
- pw.println("mClipTopAmount: " + mClipTopAmount);
- pw.println("mClipBottomAmount " + mClipBottomAmount);
- pw.println("mClipToActualHeight: " + mClipToActualHeight);
- pw.println("mExtraWidthForClipping: " + mExtraWidthForClipping);
- pw.println("mMinimumHeightForClipping: " + mMinimumHeightForClipping);
- pw.println("getClipBounds(): " + getClipBounds());
+ dumpClipping(pw, args);
}
});
}
+ protected void dumpClipping(IndentingPrintWriter pw, String[] args) {
+ pw.print("Clipping: ");
+ pw.print("mInRemovalAnimation", mInRemovalAnimation);
+ pw.print("mClipTopAmount", mClipTopAmount);
+ pw.print("mClipBottomAmount", mClipBottomAmount);
+ pw.print("mClipToActualHeight", mClipToActualHeight);
+ pw.print("mExtraWidthForClipping", mExtraWidthForClipping);
+ pw.print("mMinimumHeightForClipping", mMinimumHeightForClipping);
+ pw.print("getClipBounds()", getClipBounds());
+ pw.println();
+ }
+
/**
* return the amount that the content is translated
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 8ae6b79..d828a67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -130,6 +130,7 @@
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import com.google.errorprone.annotations.CompileTimeConstant;
@@ -628,6 +629,9 @@
@Nullable
private OnClickListener mManageButtonClickListener;
+ @Nullable
+ private WallpaperInteractor mWallpaperInteractor;
+
public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -1192,6 +1196,7 @@
if (!SceneContainerFlag.isEnabled()) {
setMaxLayoutHeight(getHeight());
updateContentHeight();
+ mWallpaperInteractor.setNotificationStackAbsoluteBottom(mContentHeight);
}
clampScrollPosition();
requestChildrenUpdate();
@@ -1253,6 +1258,7 @@
if (mAmbientState.getStackTop() != stackTop) {
mAmbientState.setStackTop(stackTop);
onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+ mWallpaperInteractor.setNotificationStackAbsoluteBottom((int) stackTop);
}
}
@@ -5898,6 +5904,10 @@
mController.getNotificationRoundnessManager().setAnimatedChildren(mChildrenToAddAnimated);
}
+ public void setWallpaperInteractor(WallpaperInteractor wallpaperInteractor) {
+ mWallpaperInteractor = wallpaperInteractor;
+ }
+
void addSwipedOutView(View v) {
mSwipedOutViews.add(v);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7b02d0c..00c5c40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -145,6 +145,7 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -226,6 +227,8 @@
private final SensitiveNotificationProtectionController
mSensitiveNotificationProtectionController;
+ private final WallpaperInteractor mWallpaperInteractor;
+
private View mLongPressedView;
private final NotificationListContainerImpl mNotificationListContainer =
@@ -756,7 +759,8 @@
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
SplitShadeStateController splitShadeStateController,
- SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
+ WallpaperInteractor wallpaperInteractor) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mViewBinder = viewBinder;
@@ -812,6 +816,7 @@
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
+ mWallpaperInteractor = wallpaperInteractor;
mView.passSplitShadeStateController(splitShadeStateController);
if (SceneContainerFlag.isEnabled()) {
mWakeUpCoordinator.setStackScroller(this);
@@ -948,6 +953,8 @@
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
this::onKeyguardTransitionChanged);
}
+
+ mView.setWallpaperInteractor(mWallpaperInteractor);
}
private boolean isInVisibleLocation(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index 65a0218..0744b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -32,19 +32,24 @@
* Note: New logic should be added to [WallpaperRepository], not this class.
*/
@SysUISingleton
-class WallpaperController @Inject constructor(
+class WallpaperController
+@Inject
+constructor(
private val wallpaperManager: WallpaperManager,
private val wallpaperRepository: WallpaperRepository,
) {
var rootView: View? = null
+ set(value) {
+ field = value
+ wallpaperRepository.rootView = value
+ }
private var notificationShadeZoomOut: Float = 0f
private var unfoldTransitionZoomOut: Float = 0f
private val shouldUseDefaultUnfoldTransition: Boolean
- get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
- ?: true
+ get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition() ?: true
fun setNotificationShadeZoom(zoomOut: Float) {
notificationShadeZoomOut = zoomOut
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index b45b8cd..54953c9 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
+import android.view.View
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,4 +34,7 @@
class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+ override var rootView: View? = null
+
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 041b6f9..203e1da 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -21,10 +21,16 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.os.Bundle
import android.os.UserHandle
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -32,16 +38,19 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** A repository storing information about the current wallpaper. */
@@ -51,6 +60,15 @@
/** Emits true if the current user's current wallpaper supports ambient mode. */
val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+
+ /** Set rootView to get its windowToken afterwards */
+ var rootView: View?
+
+ /**
+ * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
+ * this value
+ */
+ fun setNotificationStackAbsoluteBottom(bottom: Float)
}
@SysUISingleton
@@ -61,6 +79,8 @@
@Background private val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
+ keyguardRepository: KeyguardRepository,
+ keyguardClockRepository: KeyguardClockRepository,
private val wallpaperManager: WallpaperManager,
context: Context,
) : WallpaperRepository {
@@ -69,10 +89,7 @@
private val wallpaperChanged: Flow<Unit> =
broadcastDispatcher
- .broadcastFlow(
- IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
- user = UserHandle.ALL,
- )
+ .broadcastFlow(IntentFilter(Intent.ACTION_WALLPAPER_CHANGED), user = UserHandle.ALL)
// The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
// input flows emit at least once. Since this flow is an input flow, it needs to emit
// when it starts up to ensure that the `combine` will run if the user changes before we
@@ -87,6 +104,27 @@
// Only update the wallpaper status once the user selection has finished.
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+ /** The bottom of notification stack respect to the top of screen. */
+ private val notificationStackAbsoluteBottom: MutableStateFlow<Float> = MutableStateFlow(0F)
+
+ /** The top of shortcut respect to the top of screen. */
+ private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop
+
+ /**
+ * The top of notification stack to give a default state of lockscreen remaining space for
+ * states with notifications to compare with. It's the bottom of smartspace date and weather
+ * smartspace in small clock state, plus proper bottom margin.
+ */
+ private val notificationStackDefaultTop = keyguardClockRepository.notificationDefaultTop
+ @VisibleForTesting var sendLockscreenLayoutJob: Job? = null
+ private val lockscreenRemainingSpaceWithNotification: Flow<Triple<Float, Float, Float>> =
+ combine(
+ notificationStackAbsoluteBottom,
+ notificationStackDefaultTop,
+ shortcutAbsoluteTop,
+ ::Triple,
+ )
+
override val wallpaperInfo: StateFlow<WallpaperInfo?> =
if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
MutableStateFlow(null).asStateFlow()
@@ -116,9 +154,70 @@
initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
)
+ override var rootView: View? = null
+
+ val shouldSendNotificationLayout =
+ wallpaperInfo
+ .map {
+ val shouldSendNotificationLayout = shouldSendNotificationLayout(it)
+ if (shouldSendNotificationLayout) {
+ sendLockscreenLayoutJob =
+ scope.launch {
+ lockscreenRemainingSpaceWithNotification.collect {
+ (notificationBottom, notificationDefaultTop, shortcutTop) ->
+ wallpaperManager.sendWallpaperCommand(
+ /* windowToken = */ rootView?.windowToken,
+ /* action = */ WallpaperManager
+ .COMMAND_LOCKSCREEN_LAYOUT_CHANGED,
+ /* x = */ 0,
+ /* y = */ 0,
+ /* z = */ 0,
+ /* extras = */ Bundle().apply {
+ putFloat("screenLeft", 0F)
+ putFloat("smartspaceBottom", notificationDefaultTop)
+ putFloat("notificationBottom", notificationBottom)
+ putFloat(
+ "screenRight",
+ context.resources.displayMetrics.widthPixels.toFloat(),
+ )
+ putFloat("shortCutTop", shortcutTop)
+ },
+ )
+ }
+ }
+ } else {
+ sendLockscreenLayoutJob?.cancel()
+ }
+ shouldSendNotificationLayout
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly
+ else SharingStarted.Lazily,
+ initialValue = false,
+ )
+
+ override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ notificationStackAbsoluteBottom.value = bottom
+ }
+
private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
return withContext(bgDispatcher) {
wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
}
}
+
+ private fun shouldSendNotificationLayout(wallpaperInfo: WallpaperInfo?): Boolean {
+ return if (wallpaperInfo != null && wallpaperInfo.component != null) {
+ wallpaperInfo.component!!.className == MAGIC_PORTRAIT_CLASSNAME
+ } else {
+ false
+ }
+ }
+
+ companion object {
+ const val MAGIC_PORTRAIT_CLASSNAME =
+ "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
new file mode 100644
index 0000000..79ebf01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.wallpapers.domain.interactor
+
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import javax.inject.Inject
+
+class WallpaperInteractor @Inject constructor(val wallpaperRepository: WallpaperRepository) {
+ fun setNotificationStackAbsoluteBottom(bottom: Float) {
+ wallpaperRepository.setNotificationStackAbsoluteBottom(bottom)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 0b944f0..96a0aad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -39,13 +39,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +57,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@@ -122,6 +123,7 @@
{ keyguardBlueprintInteractor },
keyguardRootViewModel,
aodBurnInViewModel,
+ largeScreenHeaderHelperLazy = { mock<LargeScreenHeaderHelper>() },
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7cd306e..6425da4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -83,6 +83,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -103,7 +104,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -113,6 +113,7 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import org.junit.Before;
import org.junit.Test;
@@ -154,6 +155,7 @@
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private PowerInteractor mPowerInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock private WallpaperInteractor mWallpaperInteractor;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private ColorUpdateLogger mColorUpdateLogger;
@@ -1070,7 +1072,8 @@
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
new ResourcesSplitShadeStateController(),
- mSensitiveNotificationProtectionController);
+ mSensitiveNotificationProtectionController,
+ mWallpaperInteractor);
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 8a3e551..59fc0d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -102,6 +102,7 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
import kotlin.Unit;
@@ -146,6 +147,7 @@
@Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private NotificationShelf mNotificationShelf;
+ @Mock private WallpaperInteractor mWallpaperInteractor;
@Mock private NotificationStackSizeCalculator mStackSizeCalculator;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@@ -208,6 +210,7 @@
.thenReturn(mNotificationRoundnessManager);
mStackScroller.setController(mStackScrollLayoutController);
mStackScroller.setShelf(mNotificationShelf);
+ mStackScroller.setWallpaperInteractor(mWallpaperInteractor);
when(mStackScroller.getExpandHelper()).thenReturn(mExpandHelper);
doNothing().when(mGroupExpansionManager).collapseGroups();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index bdecf2b..b8dd334 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -18,18 +18,21 @@
import android.app.WallpaperInfo
import android.app.WallpaperManager
+import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl.Companion.MAGIC_PORTRAIT_CLASSNAME
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -39,6 +42,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@@ -48,6 +54,8 @@
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val userRepository = FakeUserRepository()
+ private val keyguardClockRepository = FakeKeyguardClockRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
private val wallpaperManager: WallpaperManager = mock()
private val underTest: WallpaperRepositoryImpl by lazy {
@@ -56,6 +64,8 @@
testDispatcher,
fakeBroadcastDispatcher,
userRepository,
+ keyguardRepository,
+ keyguardClockRepository,
wallpaperManager,
context,
)
@@ -219,7 +229,7 @@
testScope.runTest {
context.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
- false
+ false,
)
val latest by collectLastValue(underTest.wallpaperInfo)
@@ -407,7 +417,7 @@
testScope.runTest {
context.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
- false
+ false,
)
val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
@@ -425,6 +435,54 @@
assertThat(latest).isFalse()
}
+ @Test
+ @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
+ fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.shouldSendNotificationLayout)
+ val magicPortraitWallpaper =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component)
+ .thenReturn(ComponentName(context, MAGIC_PORTRAIT_CLASSNAME))
+ }
+ whenever(wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(magicPortraitWallpaper)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isTrue()
+ assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
+ assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
+ fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.shouldSendNotificationLayout)
+ val magicPortraitWallpaper = MAGIC_PORTRAIT_WP
+ whenever(wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(magicPortraitWallpaper)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isTrue()
+ assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
+ assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
+
+ val nonMagicPortraitWallpaper = UNSUPPORTED_WP
+ whenever(wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(nonMagicPortraitWallpaper)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isFalse()
+ assertThat(underTest.sendLockscreenLayoutJob?.isCancelled).isEqualTo(true)
+ }
+
private companion object {
val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
val UNSUPPORTED_WP =
@@ -433,5 +491,10 @@
val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
val SUPPORTED_WP =
mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+
+ val MAGIC_PORTRAIT_WP =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component).thenReturn(ComponentName("", MAGIC_PORTRAIT_CLASSNAME))
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt
new file mode 100644
index 0000000..2850ab7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.app
+
+import android.app.WallpaperManager
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.wallpaperManager: WallpaperManager by Fixture {
+ WallpaperManager.getInstance(applicationContext)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 5e5f8cb..159dd34 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -46,16 +46,27 @@
private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
override val previewClock: Flow<ClockController>
get() = _previewClock
+
+ private val _notificationDefaultTop = MutableStateFlow(0F)
+ override val notificationDefaultTop: StateFlow<Float>
+ get() = _notificationDefaultTop
+
override val clockEventController: ClockEventController
get() = mock()
+
override val shouldForceSmallClock: Boolean
get() = _shouldForceSmallClock
+
private var _shouldForceSmallClock: Boolean = false
override fun setClockSize(size: ClockSize) {
_clockSize.value = size
}
+ override fun setNotificationDefaultTop(top: Float) {
+ _notificationDefaultTop.value = top
+ }
+
fun setSelectedClockSize(size: ClockSizeSetting) {
_selectedClockSize.value = size
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 54a6c0c..e513e8d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -131,6 +131,10 @@
private val _isEncryptedOrLockdown = MutableStateFlow(true)
override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+ private val _shortcutAbsoluteTop = MutableStateFlow(0F)
+ override val shortcutAbsoluteTop: StateFlow<Float>
+ get() = _shortcutAbsoluteTop.asStateFlow()
+
private val _isKeyguardEnabled = MutableStateFlow(true)
override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
@@ -241,7 +245,7 @@
override fun setBiometricUnlockState(
mode: BiometricUnlockMode,
- source: BiometricUnlockSource?
+ source: BiometricUnlockSource?,
) {
_biometricUnlockState.tryEmit(BiometricUnlockModel(mode, source))
}
@@ -294,6 +298,10 @@
return isShowKeyguardWhenReenabled
}
+ override fun setShortcutAbsoluteTop(top: Float) {
+ _shortcutAbsoluteTop.value = top
+ }
+
override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
_canIgnoreAuthAndReturnToGone.value = canWake
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 12d7c49..49a8c18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -29,9 +29,10 @@
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.LargeScreenHeaderHelper
import java.util.Optional
import org.mockito.Mockito.spy
+import org.mockito.kotlin.mock
val Kosmos.keyguardClockSection: ClockSection by
Kosmos.Fixture {
@@ -43,6 +44,7 @@
blueprintInteractor = { keyguardBlueprintInteractor },
rootViewModel = keyguardRootViewModel,
aodBurnInViewModel = aodBurnInViewModel,
+ largeScreenHeaderHelperLazy = { mock<LargeScreenHeaderHelper>() },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
index d52883e..bdb9abb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
@@ -27,13 +27,13 @@
val Kosmos.keyguardClockInteractor by
Kosmos.Fixture {
KeyguardClockInteractor(
- keyguardClockRepository = keyguardClockRepository,
- applicationScope = applicationCoroutineScope,
mediaCarouselInteractor = mediaCarouselInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
shadeInteractor = shadeInteractor,
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
+ applicationScope = applicationCoroutineScope,
+ keyguardClockRepository = keyguardClockRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
new file mode 100644
index 0000000..1d8c891
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.wallpapers.data.repository
+
+import android.content.applicationContext
+import com.android.app.wallpaperManager
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.data.repository.userRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.wallpaperRepository by Fixture {
+ WallpaperRepositoryImpl(
+ context = applicationContext,
+ scope = testScope,
+ bgDispatcher = testDispatcher,
+ broadcastDispatcher = broadcastDispatcher,
+ userRepository = userRepository,
+ wallpaperManager = wallpaperManager,
+ keyguardClockRepository = keyguardClockRepository,
+ keyguardRepository = keyguardRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt
new file mode 100644
index 0000000..5278351
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.wallpapers.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.wallpapers.data.repository.wallpaperRepository
+
+val Kosmos.wallpaperInteractor by
+ Kosmos.Fixture { WallpaperInteractor(wallpaperRepository = wallpaperRepository) }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 596e375..e0cf96f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -72,9 +72,6 @@
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
-import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
-import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION;
-import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
@@ -163,7 +160,6 @@
import com.android.internal.pm.pkg.component.ParsedAttribution;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -2833,26 +2829,12 @@
@Override
public int checkOperation(int code, int uid, String packageName) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
- false);
- }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
Context.DEVICE_ID_DEFAULT, false /*raw*/);
}
@Override
public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
- false);
- }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
virtualDeviceId, false /*raw*/);
}
@@ -3033,14 +3015,6 @@
public SyncNotedAppOp noteProxyOperationWithState(int code,
AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage, boolean skipProxyOperation) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- attributionSourceState.uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION,
- attributionSourceState.attributionTag != null);
- }
-
AttributionSource attributionSource = new AttributionSource(attributionSourceState);
return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
@@ -3122,14 +3096,6 @@
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
- if (Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED,
- uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
- attributionTag != null);
- }
-
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
shouldCollectMessage);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index eeec369..99ced7f 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -212,6 +212,10 @@
Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE,
Flags::blockAutobrightnessChangesOnStylusUsage
);
+ private final FlagState mIsUserRefreshRateForExternalDisplayEnabled = new FlagState(
+ Flags.FLAG_ENABLE_USER_REFRESH_RATE_FOR_EXTERNAL_DISPLAY,
+ Flags::enableUserRefreshRateForExternalDisplay
+ );
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
@@ -456,6 +460,14 @@
}
/**
+ * @return {@code true} if need to use user refresh rate settings for
+ * external displays.
+ */
+ public boolean isUserRefreshRateForExternalDisplayEnabled() {
+ return mIsUserRefreshRateForExternalDisplayEnabled.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -501,6 +513,7 @@
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
+ pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 20ecd2d..2f04d9e 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -381,3 +381,14 @@
bug: "352411468"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_user_refresh_rate_for_external_display"
+ namespace: "display_manager"
+ description: "Apply refresh rate from user preferred display mode to external displays"
+ bug: "370657357"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 18e0d6e..ffa64bf 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1194,6 +1194,13 @@
@GuardedBy("mLock")
private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
float defaultRefreshRate, int displayId) {
+ if (mDisplayObserver.isExternalDisplayLocked(displayId)) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "skip updateRefreshRateSettingLocked for external display "
+ + displayId);
+ }
+ return;
+ }
// TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
// used to predict if we're going to be doing frequent refresh rate switching, and if
// so, enable the brightness observer. The logic here is more complicated and fragile
@@ -1243,6 +1250,8 @@
}
private void removeRefreshRateSetting(int displayId) {
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+ null);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
null);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
@@ -1458,11 +1467,11 @@
public void onDisplayAdded(int displayId) {
updateDisplayDeviceConfig(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
+ registerExternalDisplay(displayInfo);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
updateDisplaysPeakRefreshRateAndResolution(displayInfo);
- addDisplaysSynchronizedPeakRefreshRate(displayInfo);
}
@Override
@@ -1477,7 +1486,7 @@
updateLayoutLimitedFrameRate(displayId, null);
removeUserSettingDisplayPreferredSize(displayId);
removeDisplaysPeakRefreshRateAndResolution(displayId);
- removeDisplaysSynchronizedPeakRefreshRate(displayId);
+ unregisterExternalDisplay(displayId);
}
@Override
@@ -1489,6 +1498,30 @@
updateUserSettingDisplayPreferredSize(displayInfo);
}
+ private void registerExternalDisplay(DisplayInfo displayInfo) {
+ if (displayInfo == null || displayInfo.type != Display.TYPE_EXTERNAL) {
+ return;
+ }
+ synchronized (mLock) {
+ mExternalDisplaysConnected.add(displayInfo.displayId);
+ if (mExternalDisplaysConnected.size() == 1) {
+ addDisplaysSynchronizedPeakRefreshRate();
+ }
+ }
+ }
+
+ private void unregisterExternalDisplay(int displayId) {
+ synchronized (mLock) {
+ if (!isExternalDisplayLocked(displayId)) {
+ return;
+ }
+ mExternalDisplaysConnected.remove(displayId);
+ if (mExternalDisplaysConnected.isEmpty()) {
+ removeDisplaysSynchronizedPeakRefreshRate();
+ }
+ }
+ }
+
boolean isExternalDisplayLocked(int displayId) {
return mExternalDisplaysConnected.contains(displayId);
}
@@ -1534,10 +1567,24 @@
return;
}
- mVotesStorage.updateVote(info.displayId,
- Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
- Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
- /* height */ preferredMode.getPhysicalHeight()));
+ if (info.type == Display.TYPE_EXTERNAL
+ && mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()
+ && !isRefreshRateSynchronizationEnabled()) {
+ mVotesStorage.updateVote(info.displayId,
+ Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ /* minWidth */ preferredMode.getPhysicalWidth(),
+ /* minHeight */ preferredMode.getPhysicalHeight(),
+ /* width */ preferredMode.getPhysicalWidth(),
+ /* height */ preferredMode.getPhysicalHeight(),
+ /* minRefreshRate */ preferredMode.getRefreshRate(),
+ /* maxRefreshRate */ preferredMode.getRefreshRate()));
+ } else {
+ mVotesStorage.updateVote(info.displayId,
+ Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE,
+ Vote.forSize(/* width */ preferredMode.getPhysicalWidth(),
+ /* height */ preferredMode.getPhysicalHeight()));
+ }
}
@Nullable
@@ -1584,17 +1631,10 @@
* Sets 60Hz target refresh rate as the vote with
* {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority.
*/
- private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) {
- if (info == null || info.type != Display.TYPE_EXTERNAL
- || !isRefreshRateSynchronizationEnabled()) {
+ private void addDisplaysSynchronizedPeakRefreshRate() {
+ if (!isRefreshRateSynchronizationEnabled()) {
return;
}
- synchronized (mLock) {
- mExternalDisplaysConnected.add(info.displayId);
- if (mExternalDisplaysConnected.size() != 1) {
- return;
- }
- }
// set minRefreshRate as the max refresh rate.
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE,
Vote.forPhysicalRefreshRates(
@@ -1610,19 +1650,10 @@
+ SYNCHRONIZED_REFRESH_RATE_TOLERANCE));
}
- private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) {
+ private void removeDisplaysSynchronizedPeakRefreshRate() {
if (!isRefreshRateSynchronizationEnabled()) {
return;
}
- synchronized (mLock) {
- if (!isExternalDisplayLocked(displayId)) {
- return;
- }
- mExternalDisplaysConnected.remove(displayId);
- if (!mExternalDisplaysConnected.isEmpty()) {
- return;
- }
- }
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, null);
}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 459f9a6..f5abb05 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -44,7 +44,7 @@
// It votes [minRefreshRate, Float.POSITIVE_INFINITY]
int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
- // User setting preferred display resolution.
+ // User setting preferred display resolution, for external displays also includes refresh rate.
int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4;
// APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index cd0a2a7..03fc60c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -106,8 +106,7 @@
protected final String TAG = getClass().getSimpleName().replace('$', '.');
protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
- protected static final int ON_BINDING_DIED_REBIND_MSG = 1234;
+ private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
protected static final String ENABLED_SERVICES_SEPARATOR = ":";
private static final String DB_VERSION_1 = "1";
private static final String DB_VERSION_2 = "2";
@@ -857,13 +856,7 @@
String approvedItem = getApprovedValue(pkgOrComponent);
if (approvedItem != null) {
- final ComponentName component = ComponentName.unflattenFromString(approvedItem);
if (enabled) {
- if (component != null && !isValidService(component, userId)) {
- Log.e(TAG, "Skip allowing " + mConfig.caption + " " + pkgOrComponent
- + " (userSet: " + userSet + ") for invalid service");
- return;
- }
approved.add(approvedItem);
} else {
approved.remove(approvedItem);
@@ -961,7 +954,7 @@
|| isPackageOrComponentAllowed(component.getPackageName(), userId))) {
return false;
}
- return isValidService(component, userId);
+ return componentHasBindPermission(component, userId);
}
private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1313,12 +1306,11 @@
if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
final ComponentName component = ComponentName.unflattenFromString(
approvedPackageOrComponent);
- if (component != null && !isValidService(component, userId)) {
+ if (component != null && !componentHasBindPermission(component, userId)) {
approved.removeAt(j);
if (DEBUG) {
Slog.v(TAG, "Removing " + approvedPackageOrComponent
- + " from approved list; no bind permission or "
- + "service interface filter found "
+ + " from approved list; no bind permission found "
+ mConfig.bindPermission);
}
}
@@ -1337,11 +1329,6 @@
}
}
- protected boolean isValidService(ComponentName component, int userId) {
- return componentHasBindPermission(component, userId) && queryPackageForServices(
- component.getPackageName(), userId).contains(component);
- }
-
protected boolean isValidEntry(String packageOrComponent, int userId) {
return hasMatchingServices(packageOrComponent, userId);
}
@@ -1499,25 +1486,23 @@
* Called when user switched to unbind all services from other users.
*/
@VisibleForTesting
- void unbindOtherUserServices(int switchedToUser) {
+ void unbindOtherUserServices(int currentUser) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser);
- unbindServicesImpl(switchedToUser, true /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
+ unbindServicesImpl(currentUser, true /* allExceptUser */);
t.traceEnd();
}
- void unbindUserServices(int removedUser) {
+ void unbindUserServices(int user) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindUserServices" + removedUser);
- unbindServicesImpl(removedUser, false /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindUserServices" + user);
+ unbindServicesImpl(user, false /* allExceptUser */);
t.traceEnd();
}
void unbindServicesImpl(int user, boolean allExceptUser) {
final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
synchronized (mMutex) {
- // Remove enqueued rebinds to avoid rebinding services for a switched user
- mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG);
final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
for (ManagedServiceInfo info : removableBoundServices) {
if ((allExceptUser && (info.userid != user))
@@ -1712,7 +1697,6 @@
mServicesRebinding.add(servicesBindingTag);
mHandler.postDelayed(() ->
reregisterService(name, userid),
- ON_BINDING_DIED_REBIND_MSG,
ON_BINDING_DIED_REBIND_DELAY_MS);
} else {
Slog.v(TAG, getCaption() + " not rebinding in user " + userid
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index fd60e06..db57d11 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -47,6 +47,9 @@
Flags::perDisplayWakeByTouch
);
+ private final FlagState mFrameworkWakelockInfo =
+ new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo);
+
/** Returns whether early-screen-timeout-detector is enabled on not. */
public boolean isEarlyScreenTimeoutDetectorEnabled() {
return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -67,6 +70,13 @@
}
/**
+ * @return Whether FrameworkWakelockInfo atom logging is enabled or not.
+ */
+ public boolean isFrameworkWakelockInfoEnabled() {
+ return mFrameworkWakelockInfo.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -75,6 +85,7 @@
pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
pw.println(" " + mImproveWakelockLatency);
pw.println(" " + mPerDisplayWakeByTouch);
+ pw.println(" " + mFrameworkWakelockInfo);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 9cf3bb6..8bb69ba 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -26,3 +26,10 @@
bug: "343295183"
is_fixed_read_only: true
}
+
+flag {
+ name: "framework_wakelock_info"
+ namespace: "power"
+ description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
+ bug: "352602149"
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 936fadf..391071f 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -145,6 +145,7 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
+import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
@@ -5142,6 +5143,10 @@
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, true /* acquired */,
getPowerManagerWakeLockLevel(type));
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.noteStartWakeLock(
+ mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs);
+ }
}
}
@@ -5187,6 +5192,10 @@
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, false/* acquired */,
getPowerManagerWakeLockLevel(type));
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.noteStopWakeLock(
+ mapIsolatedUid(uid), name, getPowerManagerWakeLockLevel(type), uptimeMs);
+ }
if (mappedUid != uid) {
// Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -11343,6 +11352,9 @@
return mTmpCpuTimeInFreq;
}
+ WakelockStatsFrameworkEvents mFrameworkEvents = new WakelockStatsFrameworkEvents();
+ PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
+
public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
@NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
@NonNull Handler handler, @Nullable PlatformIdleStateCallback platformIdleStateCallback,
@@ -15964,6 +15976,10 @@
// Already plugged in. Schedule the long plug in alarm.
scheduleNextResetWhilePluggedInCheck();
}
+
+ if (mPowerManagerFlags.isFrameworkWakelockInfoEnabled()) {
+ mFrameworkEvents.initialize(context);
+ }
}
}
@@ -16791,7 +16807,8 @@
}
int NSORPMS = in.readInt();
if (NSORPMS > 10000) {
- throw new ParcelFormatException("File corrupt: too many screen-off rpm stats " + NSORPMS);
+ throw new ParcelFormatException(
+ "File corrupt: too many screen-off rpm stats " + NSORPMS);
}
for (int irpm = 0; irpm < NSORPMS; irpm++) {
if (in.readInt() != 0) {
diff --git a/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
new file mode 100644
index 0000000..c9693bd
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
@@ -0,0 +1,328 @@
+/*
+ * 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.server.power.stats;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** A class to initialise and log metrics pulled by statsd. */
+public class WakelockStatsFrameworkEvents {
+ // statsd has a dimensional limit on the number of different keys it can handle.
+ // Beyond that limit, statsd will drop data.
+ //
+ // When we have seem SUMMARY_THRESHOLD distinct (uid, tag, wakeLockLevel) keys,
+ // we start summarizing new keys as (uid, OVERFLOW_TAG, OVERFLOW_LEVEL) to
+ // reduce the number of keys we pass to statsd.
+ //
+ // When we reach MAX_WAKELOCK_DIMENSIONS distinct keys, we summarize all new keys
+ // as (OVERFLOW_UID, HARD_CAP_TAG, OVERFLOW_LEVEL) to hard cap the number of
+ // distinct keys we pass to statsd.
+ @VisibleForTesting public static final int SUMMARY_THRESHOLD = 500;
+ @VisibleForTesting public static final int MAX_WAKELOCK_DIMENSIONS = 1000;
+
+ @VisibleForTesting public static final int HARD_CAP_UID = -1;
+ @VisibleForTesting public static final String OVERFLOW_TAG = "*overflow*";
+ @VisibleForTesting public static final String HARD_CAP_TAG = "*overflow hard cap*";
+ @VisibleForTesting public static final int OVERFLOW_LEVEL = 1;
+
+ private static class WakeLockKey {
+ private int uid;
+ private String tag;
+ private int powerManagerWakeLockLevel;
+ private int hashCode;
+
+ WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel) {
+ this.uid = uid;
+ this.tag = new String(tag);
+ this.powerManagerWakeLockLevel = powerManagerWakeLockLevel;
+
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ int getUid() {
+ return uid;
+ }
+
+ String getTag() {
+ return tag;
+ }
+
+ int getPowerManagerWakeLockLevel() {
+ return powerManagerWakeLockLevel;
+ }
+
+ void setOverflow() {
+ tag = OVERFLOW_TAG;
+ powerManagerWakeLockLevel = OVERFLOW_LEVEL;
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ void setHardCap() {
+ uid = HARD_CAP_UID;
+ tag = HARD_CAP_TAG;
+ powerManagerWakeLockLevel = OVERFLOW_LEVEL;
+ this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof WakeLockKey)) return false;
+
+ WakeLockKey that = (WakeLockKey) o;
+ return uid == that.uid
+ && tag.equals(that.tag)
+ && powerManagerWakeLockLevel == that.powerManagerWakeLockLevel;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hashCode;
+ }
+ }
+
+ private static class WakeLockStats {
+ // accumulated uptime attributed to this WakeLock since boot, where overlap
+ // (including nesting) is ignored
+ public long uptimeMillis = 0;
+
+ // count of WakeLocks that have been acquired and then released
+ public long completedCount = 0;
+ }
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final Map<WakeLockKey, WakeLockStats> mWakeLockStats = new HashMap<>();
+
+ private static class WakeLockData {
+ // uptime millis when first acquired
+ public long acquireUptimeMillis = 0;
+ public int refCount = 0;
+
+ WakeLockData(long uptimeMillis) {
+ acquireUptimeMillis = uptimeMillis;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private final Map<WakeLockKey, WakeLockData> mOpenWakeLocks = new HashMap<>();
+
+ public void noteStartWakeLock(
+ int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
+ final WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
+
+ synchronized (mLock) {
+ WakeLockData data =
+ mOpenWakeLocks.computeIfAbsent(key, k -> new WakeLockData(eventUptimeMillis));
+ data.refCount++;
+ mOpenWakeLocks.put(key, data);
+ }
+ }
+
+ @VisibleForTesting
+ public boolean inOverflow() {
+ synchronized (mLock) {
+ return inOverflowLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean inOverflowLocked() {
+ return mWakeLockStats.size() >= SUMMARY_THRESHOLD;
+ }
+
+ @VisibleForTesting
+ public boolean inHardCap() {
+ synchronized (mLock) {
+ return inHardCapLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean inHardCapLocked() {
+ return mWakeLockStats.size() >= MAX_WAKELOCK_DIMENSIONS;
+ }
+
+ public void noteStopWakeLock(
+ int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
+ WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
+
+ synchronized (mLock) {
+ WakeLockData data = mOpenWakeLocks.get(key);
+ if (data == null) {
+ Log.e(TAG, "WakeLock not found when stopping: " + uid + " " + tag);
+ return;
+ }
+
+ if (data.refCount == 1) {
+ mOpenWakeLocks.remove(key);
+ long wakeLockDur = eventUptimeMillis - data.acquireUptimeMillis;
+
+ // Rewrite key if in an overflow state.
+ if (inOverflowLocked() && !mWakeLockStats.containsKey(key)) {
+ key.setOverflow();
+ if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) {
+ key.setHardCap();
+ }
+ }
+
+ WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
+ stats.uptimeMillis += wakeLockDur;
+ stats.completedCount++;
+ mWakeLockStats.put(key, stats);
+ } else {
+ data.refCount--;
+ mOpenWakeLocks.put(key, data);
+ }
+ }
+ }
+
+ public List<StatsEvent> pullFrameworkWakelockInfoAtoms() {
+ return pullFrameworkWakelockInfoAtoms(SystemClock.uptimeMillis());
+ }
+
+ public List<StatsEvent> pullFrameworkWakelockInfoAtoms(long nowMillis) {
+ List<StatsEvent> result = new ArrayList<>();
+ HashSet<WakeLockKey> keys = new HashSet<>();
+
+ // Used to collect open WakeLocks when in an overflow state.
+ HashMap<WakeLockKey, WakeLockStats> openOverflowStats = new HashMap<>();
+
+ synchronized (mLock) {
+ keys.addAll(mWakeLockStats.keySet());
+
+ // If we are in an overflow state, an open wakelock may have a new key
+ // that needs to be summarized.
+ if (inOverflowLocked()) {
+ for (WakeLockKey key : mOpenWakeLocks.keySet()) {
+ if (!mWakeLockStats.containsKey(key)) {
+ WakeLockData data = mOpenWakeLocks.get(key);
+
+ key.setOverflow();
+ if (inHardCapLocked() && !mWakeLockStats.containsKey(key)) {
+ key.setHardCap();
+ }
+ keys.add(key);
+
+ WakeLockStats stats =
+ openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
+ stats.uptimeMillis += nowMillis - data.acquireUptimeMillis;
+ openOverflowStats.put(key, stats);
+ }
+ }
+ } else {
+ keys.addAll(mOpenWakeLocks.keySet());
+ }
+
+ for (WakeLockKey key : keys) {
+ long openWakeLockUptime = 0;
+ WakeLockData data = mOpenWakeLocks.get(key);
+ if (data != null) {
+ openWakeLockUptime = nowMillis - data.acquireUptimeMillis;
+ }
+
+ WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
+ WakeLockStats extraTime =
+ openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
+
+ stats.uptimeMillis += openWakeLockUptime + extraTime.uptimeMillis;
+
+ StatsEvent event =
+ StatsEvent.newBuilder()
+ .setAtomId(FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO)
+ .writeInt(key.getUid())
+ .writeString(key.getTag())
+ .writeInt(key.getPowerManagerWakeLockLevel())
+ .writeLong(stats.uptimeMillis)
+ .writeLong(stats.completedCount)
+ .build();
+ result.add(event);
+ }
+ }
+
+ return result;
+ }
+
+ private static final String TAG = "BatteryStatsPulledMetrics";
+
+ private final StatsPullCallbackHandler mStatsPullCallbackHandler =
+ new StatsPullCallbackHandler();
+
+ private boolean mIsInitialized = false;
+
+ public void initialize(Context context) {
+ if (mIsInitialized) {
+ return;
+ }
+
+ final StatsManager statsManager = context.getSystemService(StatsManager.class);
+ if (statsManager == null) {
+ Log.e(
+ TAG,
+ "Error retrieving StatsManager. Cannot initialize BatteryStatsPulledMetrics.");
+ } else {
+ Log.d(TAG, "Registering callback with StatsManager");
+
+ // DIRECT_EXECUTOR means that callback will run on binder thread.
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO,
+ null /* metadata */,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ mStatsPullCallbackHandler);
+ mIsInitialized = true;
+ }
+ }
+
+ private class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
+ @Override
+ public int onPullAtom(int atomTag, List<StatsEvent> data) {
+ // handle the tags appropriately.
+ List<StatsEvent> events = pullEvents(atomTag);
+ if (events == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ data.addAll(events);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private List<StatsEvent> pullEvents(int atomTag) {
+ switch (atomTag) {
+ case FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO:
+ return pullFrameworkWakelockInfoAtoms();
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index d91f154..58f0ab4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1872,6 +1872,60 @@
}
@Test
+ public void testPeakRefreshRate_notAppliedToExternalDisplays() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ mInjector.mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ Vote vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertThat(vote1).isNull();
+ assertThat(vote2).isNull();
+
+ // Enable Smooth Display
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertThat(vote1).isNull();
+ assertThat(vote2).isNull();
+ }
+
+ @Test
public void testPeakRefreshRate_DisplayChanged() {
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
@@ -1968,8 +2022,9 @@
@Test
@Parameters({
"true, true, 60",
- "false, true, 50",
- "true, false, 50"
+ "false, true, 60",
+ "true, false, 50",
+ "false, false, 50"
})
public void testExternalDisplayMaxRefreshRate(boolean isRefreshRateSynchronizationEnabled,
boolean isExternalDisplay, float expectedMaxRenderFrameRate) {
@@ -3810,6 +3865,7 @@
SensorManagerInternal sensorManagerInternal) {
mDeviceConfig = new FakeDeviceConfig();
mDisplayInfo = new DisplayInfo();
+ mDisplayInfo.type = Display.TYPE_INTERNAL;
mDisplayInfo.defaultModeId = MODE_ID;
mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
800, 600, /* refreshRate= */ 60)};
@@ -3856,6 +3912,7 @@
@Override
public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.displayId = displayId;
return mDisplayInfoValid;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 5e240cf..e3f150e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -189,6 +189,7 @@
@Test
public void testExternalDisplay_voteUserPreferredMode() {
when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(false);
var preferredMode = mExternalDisplayModes[5];
mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
var expectedVote = Vote.forSize(
@@ -229,6 +230,108 @@
.isEqualTo(null);
}
+ /** Vote for user preferred mode */
+ @Test
+ public void testDefaultDisplay_voteUserPreferredMode() {
+ when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true);
+ var preferredMode = mInternalDisplayModes[5];
+ mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ var expectedVote = Vote.forSize(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight());
+ init();
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+ mObserver.onDisplayAdded(DEFAULT_DISPLAY);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ mInternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ mObserver.onDisplayChanged(DEFAULT_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ preferredMode = mInternalDisplayModes[4];
+ mInternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ expectedVote = Vote.forSize(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight());
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ mObserver.onDisplayChanged(DEFAULT_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ // Testing that the vote is removed.
+ mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ }
+
+ /** Vote for user preferred mode with refresh rate, synchronization vote must be disabled */
+ @Test
+ public void testExternalDisplay_voteUserPreferredMode_withRefreshRate() {
+ when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true);
+ when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(false);
+ when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true);
+ var preferredMode = mExternalDisplayModes[5];
+ mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ var expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate(),
+ preferredMode.getRefreshRate());
+ init();
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ mExternalDisplayUserPreferredModeId = INVALID_MODE_ID;
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+
+ preferredMode = mExternalDisplayModes[4];
+ mExternalDisplayUserPreferredModeId = preferredMode.getModeId();
+ expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange(
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate(),
+ preferredMode.getRefreshRate());
+ mObserver.onDisplayChanged(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(expectedVote);
+
+ // Testing that the vote is removed.
+ mObserver.onDisplayRemoved(EXTERNAL_DISPLAY);
+ assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
+ .isEqualTo(null);
+ }
+
/** External display: Do not apply limit to user preferred mode */
@Test
public void testExternalDisplay_doNotApplyLimitToUserPreferredMode() {
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index d6ca10a..fab2031 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -27,6 +27,9 @@
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "statsdprotolite",
+ "StatsdTestUtils",
+ "platformprotoslite",
],
libs: [
@@ -68,6 +71,9 @@
"androidx.test.uiautomator_uiautomator",
"modules-utils-binary-xml",
"flag-junit",
+ "statsdprotolite",
+ "StatsdTestUtils",
+ "platformprotoslite",
],
srcs: [
"src/com/android/server/power/stats/*.java",
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
new file mode 100644
index 0000000..cb644db
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
@@ -0,0 +1,433 @@
+/*
+ * 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.server.power.stats;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.WakeLockLevelEnum;
+import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.AtomsProto;
+import com.android.os.framework.FrameworkExtensionAtoms;
+import com.android.os.framework.FrameworkExtensionAtoms.FrameworkWakelockInfo;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.ExtensionRegistryLite;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WakelockStatsFrameworkEventsTest {
+ private WakelockStatsFrameworkEvents mEvents;
+ private ExtensionRegistryLite mRegistry;
+
+ @Before
+ public void setup() {
+ mEvents = new WakelockStatsFrameworkEvents();
+ mRegistry = ExtensionRegistryLite.newInstance();
+ FrameworkExtensionAtoms.registerAllExtensions(mRegistry);
+ }
+
+ private static final int UID_1 = 1;
+ private static final int UID_2 = 2;
+
+ private static final String TAG_1 = "TAG1";
+ private static final String TAG_2 = "TAG2";
+
+ private static final WakeLockLevelEnum WAKELOCK_TYPE_1 = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
+ private static final WakeLockLevelEnum WAKELOCK_TYPE_2 = WakeLockLevelEnum.DOZE_WAKE_LOCK;
+
+ private static final long TS_1 = 1000;
+ private static final long TS_2 = 2000;
+ private static final long TS_3 = 3000;
+ private static final long TS_4 = 4000;
+ private static final long TS_5 = 5000;
+
+ // Assumes that mEvents is empty.
+ private void makeMetricsAlmostOverflow() throws Exception {
+ for (int i = 0; i < mEvents.SUMMARY_THRESHOLD - 1; i++) {
+ String tag = "forceOverflow" + i;
+ mEvents.noteStartWakeLock(UID_1, tag, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, tag, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ }
+
+ assertFalse("not overflow", mEvents.inOverflow());
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo notOverflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("not overflow", notOverflowInfo, null);
+
+ // Add one more to hit an overflow state.
+ String lastTag = "forceOverflowLast";
+ mEvents.noteStartWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ assertTrue("overflow", mEvents.inOverflow());
+ info = pullResults(TS_4);
+
+ FrameworkWakelockInfo tag1Info =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(lastTag))
+ .findFirst()
+ .orElse(null);
+
+ assertTrue("lastTag found", tag1Info != null);
+ assertEquals("uid", UID_1, tag1Info.getAttributionUid());
+ assertEquals("tag", lastTag, tag1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_2, tag1Info.getType());
+ assertEquals("duration", TS_2 - TS_1, tag1Info.getUptimeMillis());
+ assertEquals("count", 1, tag1Info.getCompletedCount());
+ }
+
+ // Assumes that mEvents is empty.
+ private void makeMetricsAlmostHardCap() throws Exception {
+ for (int i = 0; i < mEvents.MAX_WAKELOCK_DIMENSIONS - 1; i++) {
+ mEvents.noteStartWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ }
+
+ assertFalse("not hard capped", mEvents.inHardCap());
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo notOverflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("not overflow", notOverflowInfo, null);
+
+ // Add one more to hit an hardcap state.
+ int hardCapUid = mEvents.MAX_WAKELOCK_DIMENSIONS;
+ mEvents.noteStartWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ assertTrue("hard capped", mEvents.inHardCap());
+ info = pullResults(TS_4);
+
+ FrameworkWakelockInfo tag2Info =
+ info.stream()
+ .filter(i -> i.getAttributionUid() == hardCapUid)
+ .findFirst()
+ .orElse(null);
+
+ assertTrue("hardCapUid found", tag2Info != null);
+ assertEquals("uid", hardCapUid, tag2Info.getAttributionUid());
+ assertEquals("tag", mEvents.OVERFLOW_TAG, tag2Info.getAttributionTag());
+ assertEquals(
+ "type", WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL), tag2Info.getType());
+ assertEquals("duration", TS_2 - TS_1, tag2Info.getUptimeMillis());
+ assertEquals("count", 1, tag2Info.getCompletedCount());
+ }
+
+ private ArrayList<FrameworkWakelockInfo> pullResults(long timestamp) throws Exception {
+ ArrayList<FrameworkWakelockInfo> result = new ArrayList<>();
+ List<StatsEvent> events = mEvents.pullFrameworkWakelockInfoAtoms(timestamp);
+
+ for (StatsEvent e : events) {
+ // The returned atom does not have external extensions registered.
+ // So we serialize and then deserialize with extensions registered.
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(e);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ CodedOutputStream codedos = CodedOutputStream.newInstance(outputStream);
+ atom.writeTo(codedos);
+ codedos.flush();
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ CodedInputStream codedis = CodedInputStream.newInstance(inputStream);
+ AtomsProto.Atom atomWithExtensions = AtomsProto.Atom.parseFrom(codedis, mRegistry);
+
+ assertTrue(
+ atomWithExtensions.hasExtension(FrameworkExtensionAtoms.frameworkWakelockInfo));
+ FrameworkWakelockInfo info =
+ atomWithExtensions.getExtension(FrameworkExtensionAtoms.frameworkWakelockInfo);
+ result.add(info);
+ }
+
+ return result;
+ }
+
+ @Test
+ public void singleWakelock() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_2 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 1, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void wakelockOpen() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_3 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 0, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void wakelockOpenOverlap() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_3);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_4 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 0, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void testOverflow() throws Exception {
+ makeMetricsAlmostOverflow();
+
+ // This one gets tagged as an overflow.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo overflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", UID_1, overflowInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ overflowInfo.getType());
+ assertEquals("duration", TS_2 - TS_1, overflowInfo.getUptimeMillis());
+ assertEquals("count", 1, overflowInfo.getCompletedCount());
+ }
+
+ @Test
+ public void testOverflowOpen() throws Exception {
+ makeMetricsAlmostOverflow();
+
+ // This is the open wakelock that overflows.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo overflowInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.OVERFLOW_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", UID_1, overflowInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ overflowInfo.getType());
+ assertEquals("duration", (TS_4 - TS_1), overflowInfo.getUptimeMillis());
+ assertEquals("count", 0, overflowInfo.getCompletedCount());
+ }
+
+ @Test
+ public void testHardCap() throws Exception {
+ makeMetricsAlmostHardCap();
+
+ // This one gets tagged as a hard cap.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_2);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo hardCapInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ hardCapInfo.getType());
+ assertEquals("duration", TS_2 - TS_1, hardCapInfo.getUptimeMillis());
+ assertEquals("count", 1, hardCapInfo.getCompletedCount());
+ }
+
+ @Test
+ public void testHardCapOpen() throws Exception {
+ makeMetricsAlmostHardCap();
+
+ // This is the open wakelock that overflows.
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2.getNumber(), TS_1);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_4);
+ FrameworkWakelockInfo hardCapInfo =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(mEvents.HARD_CAP_TAG))
+ .findFirst()
+ .orElse(null);
+
+ assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.getAttributionUid());
+ assertEquals(
+ "type",
+ WakeLockLevelEnum.forNumber(mEvents.OVERFLOW_LEVEL),
+ hardCapInfo.getType());
+ assertEquals("duration", (TS_4 - TS_1), hardCapInfo.getUptimeMillis());
+ assertEquals("count", 0, hardCapInfo.getCompletedCount());
+ }
+
+ @Test
+ public void overlappingWakelocks() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).getAttributionUid());
+ assertEquals("tag", TAG_1, info.get(0).getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).getType());
+ assertEquals("duration", TS_4 - TS_1, info.get(0).getUptimeMillis());
+ assertEquals("count", 1, info.get(0).getCompletedCount());
+ }
+
+ @Test
+ public void diffUid() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ FrameworkWakelockInfo uid1Info =
+ info.stream().filter(i -> i.getAttributionUid() == UID_1).findFirst().orElse(null);
+
+ assertTrue("UID_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.getType());
+ assertEquals("duration", TS_4 - TS_1, uid1Info.getUptimeMillis());
+ assertEquals("count", 1, uid1Info.getCompletedCount());
+
+ FrameworkWakelockInfo uid2Info =
+ info.stream().filter(i -> i.getAttributionUid() == UID_2).findFirst().orElse(null);
+ assertTrue("UID_2 found", uid2Info != null);
+ assertEquals("uid", UID_2, uid2Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid2Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid2Info.getType());
+ assertEquals("duration", TS_3 - TS_2, uid2Info.getUptimeMillis());
+ assertEquals("count", 1, uid2Info.getCompletedCount());
+ }
+
+ @Test
+ public void diffTag() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ FrameworkWakelockInfo uid1Info =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(TAG_1))
+ .findFirst()
+ .orElse(null);
+
+ assertTrue("TAG_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.getType());
+ assertEquals("duration", TS_4 - TS_1, uid1Info.getUptimeMillis());
+ assertEquals("count", 1, uid1Info.getCompletedCount());
+
+ FrameworkWakelockInfo uid2Info =
+ info.stream()
+ .filter(i -> i.getAttributionTag().equals(TAG_2))
+ .findFirst()
+ .orElse(null);
+ assertTrue("TAG_2 found", uid2Info != null);
+ assertEquals("uid", UID_1, uid2Info.getAttributionUid());
+ assertEquals("tag", TAG_2, uid2Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid2Info.getType());
+ assertEquals("duration", TS_3 - TS_2, uid2Info.getUptimeMillis());
+ assertEquals("count", 1, uid2Info.getCompletedCount());
+ }
+
+ @Test
+ public void diffType() throws Exception {
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_1);
+ mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2.getNumber(), TS_2);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2.getNumber(), TS_3);
+ mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1.getNumber(), TS_4);
+
+ ArrayList<FrameworkWakelockInfo> info = pullResults(TS_5);
+ assertEquals("size", 2, info.size());
+
+ FrameworkWakelockInfo uid1Info =
+ info.stream().filter(i -> i.getType() == WAKELOCK_TYPE_1).findFirst().orElse(null);
+
+ assertTrue("WAKELOCK_TYPE_1 found", uid1Info != null);
+ assertEquals("uid", UID_1, uid1Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid1Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_1, uid1Info.getType());
+ assertEquals("duration", TS_4 - TS_1, uid1Info.getUptimeMillis());
+ assertEquals("count", 1, uid1Info.getCompletedCount());
+
+ FrameworkWakelockInfo uid2Info =
+ info.stream().filter(i -> i.getType() == WAKELOCK_TYPE_2).findFirst().orElse(null);
+ assertTrue("WAKELOCK_TYPE_2 found", uid2Info != null);
+ assertEquals("uid", UID_1, uid2Info.getAttributionUid());
+ assertEquals("tag", TAG_1, uid2Info.getAttributionTag());
+ assertEquals("type", WAKELOCK_TYPE_2, uid2Info.getType());
+ assertEquals("duration", TS_3 - TS_2, uid2Info.getUptimeMillis());
+ assertEquals("count", 1, uid2Info.getCompletedCount());
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 3bbc6b2..48bc9d7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -63,13 +63,11 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
-import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -84,7 +82,6 @@
import com.google.android.collect.Lists;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -106,7 +103,6 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-
public class ManagedServicesTest extends UiServiceTestCase {
@Mock
@@ -119,7 +115,6 @@
private ManagedServices.UserProfiles mUserProfiles;
@Mock private DevicePolicyManager mDpm;
Object mLock = new Object();
- private TestableLooper mTestableLooper;
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
@@ -147,7 +142,6 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTestableLooper = new TestableLooper(Looper.getMainLooper());
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
@@ -205,11 +199,6 @@
mIpm, APPROVAL_BY_COMPONENT);
}
- @After
- public void tearDown() throws Exception {
- mTestableLooper.destroy();
- }
-
@Test
public void testBackupAndRestore_migration() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -899,7 +888,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -930,7 +919,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -961,7 +950,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -992,7 +981,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1064,77 +1053,6 @@
}
@Test
- public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception {
- Context context = mock(Context.class);
- PackageManager pm = mock(PackageManager.class);
- ApplicationInfo ai = new ApplicationInfo();
- ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
- when(context.getPackageName()).thenReturn(mPkg);
- when(context.getUserId()).thenReturn(mUser.getIdentifier());
- when(context.getPackageManager()).thenReturn(pm);
- when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
- APPROVAL_BY_PACKAGE);
- service = spy(service);
- ComponentName cn = ComponentName.unflattenFromString("a/a");
-
- // Trigger onBindingDied for component when registering
- // => will schedule a rebind in 10 seconds
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onBindingDied(cn);
- return true;
- });
- service.registerService(cn, 0);
- assertThat(service.isBound(cn, 0)).isFalse();
-
- // Switch to user 10
- service.onUserSwitched(10);
-
- // Check that the scheduled rebind for user 0 was cleared
- mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
- mTestableLooper.processAllMessages();
- verify(service, never()).reregisterService(any(), anyInt());
- }
-
- @Test
- public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception {
- Context context = mock(Context.class);
- PackageManager pm = mock(PackageManager.class);
- ApplicationInfo ai = new ApplicationInfo();
- ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
- when(context.getPackageName()).thenReturn(mPkg);
- when(context.getUserId()).thenReturn(mUser.getIdentifier());
- when(context.getPackageManager()).thenReturn(pm);
- when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
- APPROVAL_BY_PACKAGE);
- service = spy(service);
- ComponentName cn = ComponentName.unflattenFromString("a/a");
-
- // Trigger onBindingDied for component when registering
- // => will schedule a rebind in 10 seconds
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onBindingDied(cn);
- return true;
- });
- service.registerService(cn, 0);
- assertThat(service.isBound(cn, 0)).isFalse();
-
- // Check that the scheduled rebind is run
- mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
- mTestableLooper.processAllMessages();
- verify(service, times(1)).reregisterService(eq(cn), eq(0));
- }
-
- @Test
public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1293,64 +1211,6 @@
}
@Test
- public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
- Context context = spy(getContext());
- doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
- mIpm, APPROVAL_BY_COMPONENT);
-
- List<String> packages = new ArrayList<>();
- packages.add("package");
- addExpectedServices(service, packages, 0);
-
- final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
- final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
-
- // Both components are approved initially
- mExpectedPrimaryComponentNames.clear();
- mExpectedPrimaryPackages.clear();
- mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
- mExpectedSecondaryComponentNames.clear();
- mExpectedSecondaryPackages.clear();
-
- loadXml(service);
-
- //Component package/C1 loses serviceInterface intent filter
- ManagedServices.Config config = service.getConfig();
- when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
- .thenAnswer(new Answer<List<ResolveInfo>>() {
- @Override
- public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
- throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Intent invocationIntent = (Intent) args[0];
- if (invocationIntent != null) {
- if (invocationIntent.getAction().equals(config.serviceInterface)
- && packages.contains(invocationIntent.getPackage())) {
- List<ResolveInfo> dummyServices = new ArrayList<>();
- ResolveInfo resolveInfo = new ResolveInfo();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.packageName = invocationIntent.getPackage();
- serviceInfo.name = approvedComponent.getClassName();
- serviceInfo.permission = service.getConfig().bindPermission;
- resolveInfo.serviceInfo = serviceInfo;
- dummyServices.add(resolveInfo);
- return dummyServices;
- }
- }
- return new ArrayList<>();
- }
- });
-
- // Trigger package update
- service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
-
- assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
- assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
- }
-
- @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1363,21 +1223,6 @@
"user10package1/K", "user10.3/Component", "user10package2/L",
"user10.4/Component"}));
- // mock permissions for services
- PackageManager pm = mock(PackageManager.class);
- when(getContext().getPackageManager()).thenReturn(pm);
- List<ComponentName> enabledComponents = List.of(
- ComponentName.unflattenFromString("package/Comp"),
- ComponentName.unflattenFromString("package/C2"),
- ComponentName.unflattenFromString("again/M4"),
- ComponentName.unflattenFromString("user10package/B"),
- ComponentName.unflattenFromString("user10/Component"),
- ComponentName.unflattenFromString("user10package1/K"),
- ComponentName.unflattenFromString("user10.3/Component"),
- ComponentName.unflattenFromString("user10package2/L"),
- ComponentName.unflattenFromString("user10.4/Component"));
- mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>());
-
for (int userId : expectedEnabled.keySet()) {
ArrayList<String> expectedForUser = expectedEnabled.get(userId);
for (int i = 0; i < expectedForUser.size(); i++) {
@@ -2099,7 +1944,7 @@
metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
metaDatas.put(cn_allowed, metaDataAutobindAllow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_allowed.flattenToString(), 0, true);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2144,7 +1989,7 @@
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2183,7 +2028,7 @@
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2254,8 +2099,8 @@
}
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
- ManagedServices service, PackageManager packageManager,
- ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
+ ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
+ throws RemoteException {
when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
(Answer<ServiceInfo>) invocation -> {
ComponentName invocationCn = invocation.getArgument(0);
@@ -2270,39 +2115,6 @@
return null;
}
);
-
- // add components to queryIntentServicesAsUser response
- final List<String> packages = new ArrayList<>();
- for (ComponentName cn: componentNames) {
- packages.add(cn.getPackageName());
- }
- ManagedServices.Config config = service.getConfig();
- when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
- thenAnswer(new Answer<List<ResolveInfo>>() {
- @Override
- public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
- throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Intent invocationIntent = (Intent) args[0];
- if (invocationIntent != null) {
- if (invocationIntent.getAction().equals(config.serviceInterface)
- && packages.contains(invocationIntent.getPackage())) {
- List<ResolveInfo> dummyServices = new ArrayList<>();
- for (ComponentName cn: componentNames) {
- ResolveInfo resolveInfo = new ResolveInfo();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.packageName = invocationIntent.getPackage();
- serviceInfo.name = cn.getClassName();
- serviceInfo.permission = service.getConfig().bindPermission;
- resolveInfo.serviceInfo = serviceInfo;
- dummyServices.add(resolveInfo);
- }
- return dummyServices;
- }
- }
- return new ArrayList<>();
- }
- });
}
private void resetComponentsAndPackages() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 7e4ae67..797b95b5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -25,7 +25,6 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
@@ -194,8 +193,6 @@
public void testWriteXml_userTurnedOffNAS() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
-
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
@@ -401,10 +398,6 @@
public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
-
- doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id));
- doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id));
-
mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true,
true, true);
verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal(
@@ -550,7 +543,6 @@
public void testSetAdjustmentTypeSupportedState() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -574,7 +566,6 @@
public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -598,7 +589,6 @@
public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
diff --git a/tests/graphics/SilkFX/res/layout/view_blur_behind.xml b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml
new file mode 100644
index 0000000..83b1fa4
--- /dev/null
+++ b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml
@@ -0,0 +1,148 @@
+<?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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="I'm a little teapot" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="Something. Something." />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="I'm a little teapot" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="Something. Something." />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" />
+
+ </LinearLayout>
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="300dp" />
+
+ <com.android.test.silkfx.materials.BlurBehindContainer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#33AAAAAA"
+ android:padding="32dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="48dp"
+ android:text="Blur!" />
+
+ </com.android.test.silkfx.materials.BlurBehindContainer>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1024dp" />
+
+ </LinearLayout>
+
+ </ScrollView>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
index 59a6078..6b6d3b8 100644
--- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -61,7 +61,8 @@
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
- Demo("Background Blur", BackgroundBlurActivity::class)
+ Demo("Background Blur", BackgroundBlurActivity::class),
+ Demo("View blur behind", R.layout.view_blur_behind, commonControls = false)
))
)
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt
new file mode 100644
index 0000000..ce6348e
--- /dev/null
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.test.silkfx.materials
+
+import android.content.Context
+import android.graphics.RenderEffect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+class BlurBehindContainer(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) {
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ setBackdropRenderEffect(
+ RenderEffect.createBlurEffect(16.0f, 16.0f, Shader.TileMode.CLAMP))
+ }
+}
\ No newline at end of file