diff options
228 files changed, 8433 insertions, 2814 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index b897c1acd17f..88a3c6f97eb6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -304,6 +304,8 @@ public class JobSchedulerService extends com.android.server.SystemService private final ConnectivityController mConnectivityController; /** Need directly for sending uid state changes */ private final DeviceIdleJobsController mDeviceIdleJobsController; + /** Need direct access to this for testing. */ + private final FlexibilityController mFlexibilityController; /** Needed to get next estimated launch time. */ private final PrefetchController mPrefetchController; /** Needed to get remaining quota time. */ @@ -2701,17 +2703,16 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers = new ArrayList<StateController>(); mPrefetchController = new PrefetchController(this); mControllers.add(mPrefetchController); - final FlexibilityController flexibilityController = - new FlexibilityController(this, mPrefetchController); - mControllers.add(flexibilityController); + mFlexibilityController = new FlexibilityController(this, mPrefetchController); + mControllers.add(mFlexibilityController); mConnectivityController = - new ConnectivityController(this, flexibilityController); + new ConnectivityController(this, mFlexibilityController); mControllers.add(mConnectivityController); mControllers.add(new TimeController(this)); - final IdleController idleController = new IdleController(this, flexibilityController); + final IdleController idleController = new IdleController(this, mFlexibilityController); mControllers.add(idleController); final BatteryController batteryController = - new BatteryController(this, flexibilityController); + new BatteryController(this, mFlexibilityController); mControllers.add(batteryController); mStorageController = new StorageController(this); mControllers.add(mStorageController); @@ -5561,6 +5562,15 @@ public class JobSchedulerService extends com.android.server.SystemService return 0; } + // Shell command infrastructure: set flex policy + void setFlexPolicy(boolean override, int appliedConstraints) { + if (DEBUG) { + Slog.v(TAG, "setFlexPolicy(): " + override + "/" + appliedConstraints); + } + + mFlexibilityController.setLocalPolicyForTesting(override, appliedConstraints); + } + void setMonitorBattery(boolean enabled) { synchronized (mLock) { mBatteryStateTracker.setMonitorBatteryLocked(enabled); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index af7b27e51e20..ac240ccff017 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.UserHandle; import com.android.modules.utils.BasicShellCommandHandler; +import com.android.server.job.controllers.JobStatus; import java.io.PrintWriter; @@ -59,6 +60,10 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return cancelJob(pw); case "monitor-battery": return monitorBattery(pw); + case "disable-flex-policy": + return disableFlexPolicy(pw); + case "enable-flex-policy": + return enableFlexPolicy(pw); case "get-aconfig-flag-state": return getAconfigFlagState(pw); case "get-battery-seq": @@ -91,6 +96,8 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return resetExecutionQuota(pw); case "reset-schedule-quota": return resetScheduleQuota(pw); + case "reset-flex-policy": + return resetFlexPolicy(pw); case "stop": return stop(pw); case "trigger-dock-state": @@ -346,6 +353,65 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return 0; } + private int disableFlexPolicy(PrintWriter pw) throws Exception { + checkPermission("disable flex policy"); + + final long ident = Binder.clearCallingIdentity(); + try { + mInternal.setFlexPolicy(true, 0); + pw.println("Set flex policy to 0"); + return 0; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private int enableFlexPolicy(PrintWriter pw) throws Exception { + checkPermission("enable flex policy"); + + int enabled = 0; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-o": + case "--option": + final String constraint = getNextArgRequired(); + switch (constraint) { + case "battery-not-low": + enabled |= JobStatus.CONSTRAINT_BATTERY_NOT_LOW; + break; + case "charging": + enabled |= JobStatus.CONSTRAINT_CHARGING; + break; + case "connectivity": + enabled |= JobStatus.CONSTRAINT_CONNECTIVITY; + break; + case "idle": + enabled |= JobStatus.CONSTRAINT_IDLE; + break; + default: + pw.println("Unsupported option: " + constraint); + return -1; + } + break; + + default: + pw.println("Error: unknown option '" + opt + "'"); + return -1; + } + } + + final long ident = Binder.clearCallingIdentity(); + try { + mInternal.setFlexPolicy(true, enabled); + pw.println("Set flex policy to " + enabled); + return 0; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private int getAconfigFlagState(PrintWriter pw) throws Exception { checkPermission("get aconfig flag state", Manifest.permission.DUMP); @@ -581,6 +647,19 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { return 0; } + private int resetFlexPolicy(PrintWriter pw) throws Exception { + checkPermission("reset flex policy"); + + final long ident = Binder.clearCallingIdentity(); + try { + mInternal.setFlexPolicy(false, 0); + pw.println("Reset flex policy to its default state"); + return 0; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private int resetExecutionQuota(PrintWriter pw) throws Exception { checkPermission("reset execution quota"); @@ -773,6 +852,15 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { pw.println(" monitor-battery [on|off]"); pw.println(" Control monitoring of all battery changes. Off by default. Turning"); pw.println(" on makes get-battery-seq useful."); + pw.println(" enable-flex-policy --option <option>"); + pw.println(" Enable flex policy with the specified options. Supported options are"); + pw.println(" battery-not-low, charging, connectivity, idle."); + pw.println(" Multiple enable options can be specified (e.g."); + pw.println(" enable-flex-policy --option battery-not-low --option charging"); + pw.println(" disable-flex-policy"); + pw.println(" Turn off flex policy so that it does not affect job execution."); + pw.println(" reset-flex-policy"); + pw.println(" Resets the flex policy to its default state."); pw.println(" get-aconfig-flag-state FULL_FLAG_NAME"); pw.println(" Return the state of the specified aconfig flag, if known. The flag name"); pw.println(" must be fully qualified."); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index ee9400fb8408..852b00b38347 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -328,6 +328,9 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") private final ArraySet<String> mPackagesToCheck = new ArraySet<>(); + @GuardedBy("mLock") + private boolean mLocalOverride; + public FlexibilityController( JobSchedulerService service, PrefetchController prefetchController) { super(service); @@ -1923,6 +1926,27 @@ public final class FlexibilityController extends StateController { } } + /** + * If {@code override} is true, uses {@code appliedConstraints} for flex policy evaluation, + * overriding anything else that was set. If {@code override} is false, any previous calls + * will be discarded and the policy will be reset to the normal default policy. + */ + public void setLocalPolicyForTesting(boolean override, int appliedConstraints) { + synchronized (mLock) { + final boolean recheckJobs = mLocalOverride != override + || mAppliedConstraints != appliedConstraints; + mLocalOverride = override; + if (mLocalOverride) { + mAppliedConstraints = appliedConstraints; + } else { + mAppliedConstraints = mFcConfig.APPLIED_CONSTRAINTS; + } + if (recheckJobs) { + mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget(); + } + } + } + @Override @GuardedBy("mLock") public void dumpConstants(IndentingPrintWriter pw) { @@ -1932,6 +1956,12 @@ public final class FlexibilityController extends StateController { @Override @GuardedBy("mLock") public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { + if (mLocalOverride) { + pw.println("Local override active"); + } + pw.print("Applied Flexible Constraints:"); + JobStatus.dumpConstraints(pw, mAppliedConstraints); + pw.println(); pw.print("Satisfied Flexible Constraints:"); JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints); pw.println(); diff --git a/core/api/current.txt b/core/api/current.txt index b8c2d90e23c6..1cfc02531ebc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9242,140 +9242,140 @@ package android.app.people { package android.app.slice { - public final class Slice implements android.os.Parcelable { - ctor protected Slice(android.os.Parcel); - method public int describeContents(); - method public java.util.List<java.lang.String> getHints(); - method public java.util.List<android.app.slice.SliceItem> getItems(); - method @Nullable public android.app.slice.SliceSpec getSpec(); - method public android.net.Uri getUri(); - method public boolean isCallerNeeded(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR; - field public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; - field public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; - field public static final String HINT_ACTIONS = "actions"; - field public static final String HINT_ERROR = "error"; - field public static final String HINT_HORIZONTAL = "horizontal"; - field public static final String HINT_KEYWORDS = "keywords"; - field public static final String HINT_LARGE = "large"; - field public static final String HINT_LAST_UPDATED = "last_updated"; - field public static final String HINT_LIST = "list"; - field public static final String HINT_LIST_ITEM = "list_item"; - field public static final String HINT_NO_TINT = "no_tint"; - field public static final String HINT_PARTIAL = "partial"; - field public static final String HINT_PERMISSION_REQUEST = "permission_request"; - field public static final String HINT_SEE_MORE = "see_more"; - field public static final String HINT_SELECTED = "selected"; - field public static final String HINT_SHORTCUT = "shortcut"; - field public static final String HINT_SUMMARY = "summary"; - field public static final String HINT_TITLE = "title"; - field public static final String HINT_TTL = "ttl"; - field public static final String SUBTYPE_COLOR = "color"; - field public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; - field public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction"; - field public static final String SUBTYPE_MAX = "max"; - field public static final String SUBTYPE_MESSAGE = "message"; - field public static final String SUBTYPE_MILLIS = "millis"; - field public static final String SUBTYPE_PRIORITY = "priority"; - field public static final String SUBTYPE_RANGE = "range"; - field public static final String SUBTYPE_SOURCE = "source"; - field public static final String SUBTYPE_TOGGLE = "toggle"; - field public static final String SUBTYPE_VALUE = "value"; - } - - public static class Slice.Builder { - ctor public Slice.Builder(@NonNull android.net.Uri, android.app.slice.SliceSpec); - ctor public Slice.Builder(@NonNull android.app.slice.Slice.Builder); - method public android.app.slice.Slice.Builder addAction(@NonNull android.app.PendingIntent, @NonNull android.app.slice.Slice, @Nullable String); - method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, @Nullable String, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, @Nullable String, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addInt(int, @Nullable String, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addLong(long, @Nullable String, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, @Nullable String, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addSubSlice(@NonNull android.app.slice.Slice, @Nullable String); - method public android.app.slice.Slice.Builder addText(CharSequence, @Nullable String, java.util.List<java.lang.String>); - method public android.app.slice.Slice build(); - method public android.app.slice.Slice.Builder setCallerNeeded(boolean); - } - - public final class SliceItem implements android.os.Parcelable { - method public int describeContents(); - method public android.app.PendingIntent getAction(); - method public android.os.Bundle getBundle(); - method public String getFormat(); - method @NonNull public java.util.List<java.lang.String> getHints(); - method public android.graphics.drawable.Icon getIcon(); - method public int getInt(); - method public long getLong(); - method public android.app.RemoteInput getRemoteInput(); - method public android.app.slice.Slice getSlice(); - method public String getSubType(); - method public CharSequence getText(); - method public boolean hasHint(String); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR; - field public static final String FORMAT_ACTION = "action"; - field public static final String FORMAT_BUNDLE = "bundle"; - field public static final String FORMAT_IMAGE = "image"; - field public static final String FORMAT_INT = "int"; - field public static final String FORMAT_LONG = "long"; - field public static final String FORMAT_REMOTE_INPUT = "input"; - field public static final String FORMAT_SLICE = "slice"; - field public static final String FORMAT_TEXT = "text"; - } - - public class SliceManager { - method @Nullable public android.app.slice.Slice bindSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>); - method @Nullable public android.app.slice.Slice bindSlice(@NonNull android.content.Intent, @NonNull java.util.Set<android.app.slice.SliceSpec>); - method public int checkSlicePermission(@NonNull android.net.Uri, int, int); - method @NonNull public java.util.List<android.net.Uri> getPinnedSlices(); - method @NonNull public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri); - method @NonNull @WorkerThread public java.util.Collection<android.net.Uri> getSliceDescendants(@NonNull android.net.Uri); - method public void grantSlicePermission(@NonNull String, @NonNull android.net.Uri); - method @Nullable public android.net.Uri mapIntentToUri(@NonNull android.content.Intent); - method public void pinSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>); - method public void revokeSlicePermission(@NonNull String, @NonNull android.net.Uri); - method public void unpinSlice(@NonNull android.net.Uri); - field public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE"; - field public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; - } - - public class SliceMetrics { - ctor public SliceMetrics(@NonNull android.content.Context, @NonNull android.net.Uri); - method public void logHidden(); - method public void logTouch(int, @NonNull android.net.Uri); - method public void logVisible(); - } - - public abstract class SliceProvider extends android.content.ContentProvider { - ctor public SliceProvider(@NonNull java.lang.String...); - ctor public SliceProvider(); - method public final int delete(android.net.Uri, String, String[]); - method public final String getType(android.net.Uri); - method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>); - method @NonNull public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri); - method @NonNull public java.util.Collection<android.net.Uri> onGetSliceDescendants(@NonNull android.net.Uri); - method @NonNull public android.net.Uri onMapIntentToUri(android.content.Intent); - method public void onSlicePinned(android.net.Uri); - method public void onSliceUnpinned(android.net.Uri); - method public final android.database.Cursor query(android.net.Uri, String[], String, String[], String); - method public final android.database.Cursor query(android.net.Uri, String[], String, String[], String, android.os.CancellationSignal); - method public final android.database.Cursor query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal); - method public final int update(android.net.Uri, android.content.ContentValues, String, String[]); - field public static final String SLICE_TYPE = "vnd.android.slice"; - } - - public final class SliceSpec implements android.os.Parcelable { - ctor public SliceSpec(@NonNull String, int); - method public boolean canRender(@NonNull android.app.slice.SliceSpec); - method public int describeContents(); - method public int getRevision(); - method public String getType(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR; + @Deprecated public final class Slice implements android.os.Parcelable { + ctor @Deprecated protected Slice(android.os.Parcel); + method @Deprecated public int describeContents(); + method @Deprecated public java.util.List<java.lang.String> getHints(); + method @Deprecated public java.util.List<android.app.slice.SliceItem> getItems(); + method @Deprecated @Nullable public android.app.slice.SliceSpec getSpec(); + method @Deprecated public android.net.Uri getUri(); + method @Deprecated public boolean isCallerNeeded(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR; + field @Deprecated public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; + field @Deprecated public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; + field @Deprecated public static final String HINT_ACTIONS = "actions"; + field @Deprecated public static final String HINT_ERROR = "error"; + field @Deprecated public static final String HINT_HORIZONTAL = "horizontal"; + field @Deprecated public static final String HINT_KEYWORDS = "keywords"; + field @Deprecated public static final String HINT_LARGE = "large"; + field @Deprecated public static final String HINT_LAST_UPDATED = "last_updated"; + field @Deprecated public static final String HINT_LIST = "list"; + field @Deprecated public static final String HINT_LIST_ITEM = "list_item"; + field @Deprecated public static final String HINT_NO_TINT = "no_tint"; + field @Deprecated public static final String HINT_PARTIAL = "partial"; + field @Deprecated public static final String HINT_PERMISSION_REQUEST = "permission_request"; + field @Deprecated public static final String HINT_SEE_MORE = "see_more"; + field @Deprecated public static final String HINT_SELECTED = "selected"; + field @Deprecated public static final String HINT_SHORTCUT = "shortcut"; + field @Deprecated public static final String HINT_SUMMARY = "summary"; + field @Deprecated public static final String HINT_TITLE = "title"; + field @Deprecated public static final String HINT_TTL = "ttl"; + field @Deprecated public static final String SUBTYPE_COLOR = "color"; + field @Deprecated public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; + field @Deprecated public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction"; + field @Deprecated public static final String SUBTYPE_MAX = "max"; + field @Deprecated public static final String SUBTYPE_MESSAGE = "message"; + field @Deprecated public static final String SUBTYPE_MILLIS = "millis"; + field @Deprecated public static final String SUBTYPE_PRIORITY = "priority"; + field @Deprecated public static final String SUBTYPE_RANGE = "range"; + field @Deprecated public static final String SUBTYPE_SOURCE = "source"; + field @Deprecated public static final String SUBTYPE_TOGGLE = "toggle"; + field @Deprecated public static final String SUBTYPE_VALUE = "value"; + } + + @Deprecated public static class Slice.Builder { + ctor @Deprecated public Slice.Builder(@NonNull android.net.Uri, android.app.slice.SliceSpec); + ctor @Deprecated public Slice.Builder(@NonNull android.app.slice.Slice.Builder); + method @Deprecated public android.app.slice.Slice.Builder addAction(@NonNull android.app.PendingIntent, @NonNull android.app.slice.Slice, @Nullable String); + method @Deprecated public android.app.slice.Slice.Builder addBundle(android.os.Bundle, @Nullable String, java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, @Nullable String, java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice.Builder addInt(int, @Nullable String, java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice.Builder addLong(long, @Nullable String, java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, @Nullable String, java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice.Builder addSubSlice(@NonNull android.app.slice.Slice, @Nullable String); + method @Deprecated public android.app.slice.Slice.Builder addText(CharSequence, @Nullable String, java.util.List<java.lang.String>); + method @Deprecated public android.app.slice.Slice build(); + method @Deprecated public android.app.slice.Slice.Builder setCallerNeeded(boolean); + } + + @Deprecated public final class SliceItem implements android.os.Parcelable { + method @Deprecated public int describeContents(); + method @Deprecated public android.app.PendingIntent getAction(); + method @Deprecated public android.os.Bundle getBundle(); + method @Deprecated public String getFormat(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getHints(); + method @Deprecated public android.graphics.drawable.Icon getIcon(); + method @Deprecated public int getInt(); + method @Deprecated public long getLong(); + method @Deprecated public android.app.RemoteInput getRemoteInput(); + method @Deprecated public android.app.slice.Slice getSlice(); + method @Deprecated public String getSubType(); + method @Deprecated public CharSequence getText(); + method @Deprecated public boolean hasHint(String); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR; + field @Deprecated public static final String FORMAT_ACTION = "action"; + field @Deprecated public static final String FORMAT_BUNDLE = "bundle"; + field @Deprecated public static final String FORMAT_IMAGE = "image"; + field @Deprecated public static final String FORMAT_INT = "int"; + field @Deprecated public static final String FORMAT_LONG = "long"; + field @Deprecated public static final String FORMAT_REMOTE_INPUT = "input"; + field @Deprecated public static final String FORMAT_SLICE = "slice"; + field @Deprecated public static final String FORMAT_TEXT = "text"; + } + + @Deprecated public class SliceManager { + method @Deprecated @Nullable public android.app.slice.Slice bindSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>); + method @Deprecated @Nullable public android.app.slice.Slice bindSlice(@NonNull android.content.Intent, @NonNull java.util.Set<android.app.slice.SliceSpec>); + method @Deprecated public int checkSlicePermission(@NonNull android.net.Uri, int, int); + method @Deprecated @NonNull public java.util.List<android.net.Uri> getPinnedSlices(); + method @Deprecated @NonNull public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri); + method @Deprecated @NonNull @WorkerThread public java.util.Collection<android.net.Uri> getSliceDescendants(@NonNull android.net.Uri); + method @Deprecated public void grantSlicePermission(@NonNull String, @NonNull android.net.Uri); + method @Deprecated @Nullable public android.net.Uri mapIntentToUri(@NonNull android.content.Intent); + method @Deprecated public void pinSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>); + method @Deprecated public void revokeSlicePermission(@NonNull String, @NonNull android.net.Uri); + method @Deprecated public void unpinSlice(@NonNull android.net.Uri); + field @Deprecated public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE"; + field @Deprecated public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; + } + + @Deprecated public class SliceMetrics { + ctor @Deprecated public SliceMetrics(@NonNull android.content.Context, @NonNull android.net.Uri); + method @Deprecated public void logHidden(); + method @Deprecated public void logTouch(int, @NonNull android.net.Uri); + method @Deprecated public void logVisible(); + } + + @Deprecated public abstract class SliceProvider extends android.content.ContentProvider { + ctor @Deprecated public SliceProvider(@NonNull java.lang.String...); + ctor @Deprecated public SliceProvider(); + method @Deprecated public final int delete(android.net.Uri, String, String[]); + method @Deprecated public final String getType(android.net.Uri); + method @Deprecated public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); + method @Deprecated public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>); + method @Deprecated @NonNull public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri); + method @Deprecated @NonNull public java.util.Collection<android.net.Uri> onGetSliceDescendants(@NonNull android.net.Uri); + method @Deprecated @NonNull public android.net.Uri onMapIntentToUri(android.content.Intent); + method @Deprecated public void onSlicePinned(android.net.Uri); + method @Deprecated public void onSliceUnpinned(android.net.Uri); + method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], String, String[], String); + method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], String, String[], String, android.os.CancellationSignal); + method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal); + method @Deprecated public final int update(android.net.Uri, android.content.ContentValues, String, String[]); + field @Deprecated public static final String SLICE_TYPE = "vnd.android.slice"; + } + + @Deprecated public final class SliceSpec implements android.os.Parcelable { + ctor @Deprecated public SliceSpec(@NonNull String, int); + method @Deprecated public boolean canRender(@NonNull android.app.slice.SliceSpec); + method @Deprecated public int describeContents(); + method @Deprecated public int getRevision(); + method @Deprecated public String getType(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR; } } @@ -17982,7 +17982,6 @@ package android.graphics.fonts { method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font); method @NonNull public android.graphics.fonts.FontFamily build(); method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily(); - method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean canBuildVariableFamily(); } public final class FontStyle { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5547028e489f..501203efc2ef 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2186,7 +2186,7 @@ package android.app.contextualsearch { field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR; } - @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager { + @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchManager { method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int); field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2 diff --git a/core/java/android/accessibilityservice/BrailleDisplayController.java b/core/java/android/accessibilityservice/BrailleDisplayController.java index 5282aa3fccf7..7334676c905d 100644 --- a/core/java/android/accessibilityservice/BrailleDisplayController.java +++ b/core/java/android/accessibilityservice/BrailleDisplayController.java @@ -305,4 +305,6 @@ public interface BrailleDisplayController { @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID) @TestApi String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID"; + /** @hide */ + String TEST_BRAILLE_DISPLAY_NAME = "NAME"; } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 41b97d0ad5d2..97c2e43a1db6 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -42,6 +42,7 @@ per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS # ActivityThread per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS per-file ActivityThread.java = file:/services/core/java/com/android/server/wm/OWNERS +per-file ActivityThread.java = file:RESOURCES_OWNERS # Alarm per-file *Alarm* = file:/apex/jobscheduler/OWNERS diff --git a/core/java/android/app/RESOURCES_OWNERS b/core/java/android/app/RESOURCES_OWNERS index 558280396348..fe37c0ccf5de 100644 --- a/core/java/android/app/RESOURCES_OWNERS +++ b/core/java/android/app/RESOURCES_OWNERS @@ -1,3 +1,5 @@ -rtmitchell@google.com -toddke@google.com +zyy@google.com +jakmcbane@google.com +branliu@google.com +markpun@google.com patb@google.com diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 4fa45be57a11..be0aaff95c96 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -174,6 +174,17 @@ flag { } flag { + name: "copy_account_with_retry_enabled" + namespace: "enterprise" + description: "Retry copy and remove account from personal to work profile in case of failure" + bug: "329424312" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + +flag { name: "esim_management_ux_enabled" namespace: "enterprise" description: "Enable UX changes for esim management" diff --git a/core/java/android/app/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java index 0bbd1e546e5d..378193f8834c 100644 --- a/core/java/android/app/contextualsearch/CallbackToken.java +++ b/core/java/android/app/contextualsearch/CallbackToken.java @@ -51,6 +51,7 @@ public final class CallbackToken implements Parcelable { private static final String TAG = CallbackToken.class.getSimpleName(); private final IBinder mToken; + private final Object mLock = new Object(); private boolean mTokenUsed = false; public CallbackToken() { @@ -75,10 +76,14 @@ public final class CallbackToken implements Parcelable { public void getContextualSearchState(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) { if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + mToken); - if (mTokenUsed) { + boolean tokenUsed; + synchronized (mLock) { + tokenUsed = markUsedLocked(); + } + if (tokenUsed) { callback.onError(new IllegalAccessException("Token already used.")); + return; } - mTokenUsed = true; try { // Get the service from the system server. IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE); @@ -96,6 +101,12 @@ public final class CallbackToken implements Parcelable { } } + private boolean markUsedLocked() { + boolean oldValue = mTokenUsed; + mTokenUsed = true; + return oldValue; + } + /** * Return the token necessary for validating the caller of {@link #getContextualSearchState}. * diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index a894a0e27b95..c080a6b423c5 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -43,7 +43,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemApi @FlaggedApi(Flags.FLAG_ENABLE_SERVICE) -public class ContextualSearchManager { +public final class ContextualSearchManager { /** * Key to get the entrypoint from the extras of the activity launched by contextual search. diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 475ee7a1ce3d..5514868a32f5 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -41,7 +41,12 @@ import java.util.Objects; * * <p>They are constructed using {@link Builder} in a tree structure * that provides the OS some information about how the content should be displayed. + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ +@Deprecated public final class Slice implements Parcelable { /** @@ -338,7 +343,12 @@ public final class Slice implements Parcelable { /** * A Builder used to construct {@link Slice}s + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ + @Deprecated public static class Builder { private final Uri mUri; diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java index 2d6f4a6fd227..27c726dac4e5 100644 --- a/core/java/android/app/slice/SliceItem.java +++ b/core/java/android/app/slice/SliceItem.java @@ -53,7 +53,12 @@ import java.util.List; * The hints that a {@link SliceItem} are a set of strings which annotate * the content. The hints that are guaranteed to be understood by the system * are defined on {@link Slice}. + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ +@Deprecated public final class SliceItem implements Parcelable { private static final String TAG = "SliceItem"; diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java index 2e179d063b76..4fc2a0ce2286 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -59,7 +59,12 @@ import java.util.Set; * Class to handle interactions with {@link Slice}s. * <p> * The SliceManager manages permissions and pinned state for slices. + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ +@Deprecated @SystemService(Context.SLICE_SERVICE) public class SliceManager { diff --git a/core/java/android/app/slice/SliceMetrics.java b/core/java/android/app/slice/SliceMetrics.java index 746beaf939d9..abfe3a2fdc75 100644 --- a/core/java/android/app/slice/SliceMetrics.java +++ b/core/java/android/app/slice/SliceMetrics.java @@ -31,7 +31,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; * not need to reference this class. * * @see androidx.slice.widget.SliceView + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ +@Deprecated public class SliceMetrics { private static final String TAG = "SliceMetrics"; diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 42c3aa6aa0ea..4374550c3933 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -91,7 +91,12 @@ import java.util.Set; * </pre> * * @see Slice + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ +@Deprecated public abstract class SliceProvider extends ContentProvider { /** * This is the Android platform's MIME type for a URI diff --git a/core/java/android/app/slice/SliceSpec.java b/core/java/android/app/slice/SliceSpec.java index a33234981059..078f552bb681 100644 --- a/core/java/android/app/slice/SliceSpec.java +++ b/core/java/android/app/slice/SliceSpec.java @@ -38,7 +38,12 @@ import android.os.Parcelable; * * @see Slice * @see SliceProvider#onBindSlice(Uri, Set) + * @deprecated Slice framework has been deprecated, it will not receive any updates from + * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a + * framework that sends displayable data from one app to another, consider using + * {@link android.app.appsearch.AppSearchManager}. */ +@Deprecated public final class SliceSpec implements Parcelable { private final String mType; diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 689e343bcbc6..64fc4c29db90 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -333,14 +333,12 @@ public final class DeviceState { private final ArraySet<@PhysicalDeviceStateProperties Integer> mPhysicalProperties; private Configuration(int identifier, @NonNull String name, - @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties, - @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) { + @NonNull ArraySet<@SystemDeviceStateProperties Integer> systemProperties, + @NonNull ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties) { mIdentifier = identifier; mName = name; - mSystemProperties = new ArraySet<@SystemDeviceStateProperties Integer>( - systemProperties); - mPhysicalProperties = new ArraySet<@PhysicalDeviceStateProperties Integer>( - physicalProperties); + mSystemProperties = systemProperties; + mPhysicalProperties = physicalProperties; } /** Returns the unique identifier for the device state. */ @@ -479,8 +477,8 @@ public final class DeviceState { */ @NonNull public DeviceState.Configuration build() { - return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties, - mPhysicalProperties); + return new DeviceState.Configuration(mIdentifier, mName, + new ArraySet<>(mSystemProperties), new ArraySet<>(mPhysicalProperties)); } } } diff --git a/core/java/android/hardware/devicestate/DeviceStateInfo.java b/core/java/android/hardware/devicestate/DeviceStateInfo.java index c319c893aaab..28561ec37ee6 100644 --- a/core/java/android/hardware/devicestate/DeviceStateInfo.java +++ b/core/java/android/hardware/devicestate/DeviceStateInfo.java @@ -25,7 +25,6 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -77,9 +76,11 @@ public final class DeviceStateInfo implements Parcelable { * NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the * supplied parameters. */ - public DeviceStateInfo(@NonNull List<DeviceState> supportedStates, DeviceState baseState, + // Using the specific types to avoid virtual method calls in binder transactions + @SuppressWarnings("NonApiType") + public DeviceStateInfo(@NonNull ArrayList<DeviceState> supportedStates, DeviceState baseState, DeviceState state) { - this.supportedStates = new ArrayList<>(supportedStates); + this.supportedStates = supportedStates; this.baseState = baseState; this.currentState = state; } @@ -89,13 +90,13 @@ public final class DeviceStateInfo implements Parcelable { * the fields of the returned instance. */ public DeviceStateInfo(@NonNull DeviceStateInfo info) { - this(List.copyOf(info.supportedStates), info.baseState, info.currentState); + this(new ArrayList<>(info.supportedStates), info.baseState, info.currentState); } @Override public boolean equals(@Nullable Object other) { if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; + if (other == null || (getClass() != other.getClass())) return false; DeviceStateInfo that = (DeviceStateInfo) other; return baseState.equals(that.baseState) && currentState.equals(that.currentState) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index dd548c6f9da8..a0733d445257 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -110,6 +110,7 @@ import static android.view.accessibility.Flags.reduceWindowContentChangedEventTh import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; +import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; @@ -1148,6 +1149,7 @@ public final class ViewRootImpl implements ViewParent, private String mLargestViewTraceName; private static boolean sToolkitSetFrameRateReadOnlyFlagValue; + private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; private static boolean sToolkitFrameRateTypingReadOnlyFlagValue; @@ -1155,6 +1157,8 @@ public final class ViewRootImpl implements ViewParent, sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision(); sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly(); + sToolkitFrameRateFunctionEnablingReadOnlyFlagValue = + toolkitFrameRateFunctionEnablingReadOnly(); } // The latest input event from the gesture that was used to resolve the pointer icon. @@ -12788,7 +12792,9 @@ public final class ViewRootImpl implements ViewParent, private boolean shouldEnableDvrr() { // uncomment this when we are ready for enabling dVRR - // return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced(); + if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { + return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced(); + } return false; } diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 06598b3dfdbd..1d4d18b120c9 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -86,4 +86,12 @@ flag { description: "Feature flag for suppressing boost on typing" bug: "239979904" is_fixed_read_only: true +} + +flag { + name: "toolkit_frame_rate_function_enabling_read_only" + namespace: "toolkit" + description: "Feature flag to enable the functionality of the dVRR feature" + bug: "239979904" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index f54ef3868f53..ab6b5122ea05 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -16,6 +16,8 @@ package android.widget; +import static android.view.flags.Flags.viewVelocityApi; + import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.NonNull; @@ -5098,6 +5100,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te boolean more = scroller.computeScrollOffset(); final int y = scroller.getCurrY(); + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } + // Flip sign to convert finger direction to list items direction // (e.g. finger moving down means list is moving towards the top) int delta = consumeFlingInStretch(mLastFlingY - y); @@ -5192,6 +5199,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te invalidate(); postOnAnimation(this); } + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } } else { endFling(); } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 7fbec67ec4e9..fa0dab09a8b3 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -86,3 +86,10 @@ flag { description: "Whether the blurred letterbox wallpaper background is enabled by default" bug: "297195682" } + +flag { + name: "enable_compatui_sysui_launcher" + namespace: "large_screen_experiences_app_compat" + description: "Enables sysui animation for user aspect ratio button" + bug: "300357441" +}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java index ce8ca0d781e4..2d365368a013 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java @@ -21,6 +21,11 @@ import java.util.List; * Interface for the companion operations */ public interface CompanionOperation { + /** + * Read, create and add instance to operations + * @param buffer data to read to create operation + * @param operations command is to be added + */ void read(WireBuffer buffer, List<Operation> operations); // Debugging / Documentation utility functions diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index 0e4c7430afb5..55f2dee95a34 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -331,6 +331,7 @@ public class CoreDocument { public void initFromBuffer(RemoteComposeBuffer buffer) { mOperations = new ArrayList<Operation>(); buffer.inflateFromBuffer(mOperations); + mBuffer = buffer; } /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index b8bb1f0f3519..54b277a2ac58 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -17,8 +17,29 @@ package com.android.internal.widget.remotecompose.core; import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; +import com.android.internal.widget.remotecompose.core.operations.ClipPath; +import com.android.internal.widget.remotecompose.core.operations.ClipRect; +import com.android.internal.widget.remotecompose.core.operations.DrawArc; +import com.android.internal.widget.remotecompose.core.operations.DrawBitmap; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt; +import com.android.internal.widget.remotecompose.core.operations.DrawCircle; +import com.android.internal.widget.remotecompose.core.operations.DrawLine; +import com.android.internal.widget.remotecompose.core.operations.DrawOval; +import com.android.internal.widget.remotecompose.core.operations.DrawPath; +import com.android.internal.widget.remotecompose.core.operations.DrawRect; +import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect; +import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath; +import com.android.internal.widget.remotecompose.core.operations.DrawTextRun; +import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; import com.android.internal.widget.remotecompose.core.operations.Header; +import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; +import com.android.internal.widget.remotecompose.core.operations.MatrixRotate; +import com.android.internal.widget.remotecompose.core.operations.MatrixSave; +import com.android.internal.widget.remotecompose.core.operations.MatrixScale; +import com.android.internal.widget.remotecompose.core.operations.MatrixSkew; +import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; +import com.android.internal.widget.remotecompose.core.operations.PaintData; +import com.android.internal.widget.remotecompose.core.operations.PathData; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.core.operations.RootContentDescription; import com.android.internal.widget.remotecompose.core.operations.TextData; @@ -48,7 +69,30 @@ public class Operations { public static final int DATA_BITMAP = 101; public static final int DATA_TEXT = 102; +/////////////////////////////===================== + public static final int CLIP_PATH = 38; + public static final int CLIP_RECT = 39; + public static final int PAINT_VALUES = 40; + public static final int DRAW_RECT = 42; + public static final int DRAW_TEXT_RUN = 43; + public static final int DRAW_CIRCLE = 46; + public static final int DRAW_LINE = 47; + public static final int DRAW_ROUND_RECT = 51; + public static final int DRAW_ARC = 52; + public static final int DRAW_TEXT_ON_PATH = 53; + public static final int DRAW_OVAL = 56; + public static final int DATA_PATH = 123; + public static final int DRAW_PATH = 124; + public static final int DRAW_TWEEN_PATH = 125; + public static final int MATRIX_SCALE = 126; + public static final int MATRIX_TRANSLATE = 127; + public static final int MATRIX_SKEW = 128; + public static final int MATRIX_ROTATE = 129; + public static final int MATRIX_SAVE = 130; + public static final int MATRIX_RESTORE = 131; + public static final int MATRIX_SET = 132; + /////////////////////////////////////////====================== public static IntMap<CompanionOperation> map = new IntMap<>(); static { @@ -60,6 +104,29 @@ public class Operations { map.put(CLICK_AREA, ClickArea.COMPANION); map.put(ROOT_CONTENT_BEHAVIOR, RootContentBehavior.COMPANION); map.put(ROOT_CONTENT_DESCRIPTION, RootContentDescription.COMPANION); + + map.put(DRAW_ARC, DrawArc.COMPANION); + map.put(DRAW_BITMAP, DrawBitmap.COMPANION); + map.put(DRAW_CIRCLE, DrawCircle.COMPANION); + map.put(DRAW_LINE, DrawLine.COMPANION); + map.put(DRAW_OVAL, DrawOval.COMPANION); + map.put(DRAW_PATH, DrawPath.COMPANION); + map.put(DRAW_RECT, DrawRect.COMPANION); + map.put(DRAW_ROUND_RECT, DrawRoundRect.COMPANION); + map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath.COMPANION); + map.put(DRAW_TEXT_RUN, DrawTextRun.COMPANION); + map.put(DRAW_TWEEN_PATH, DrawTweenPath.COMPANION); + map.put(DATA_PATH, PathData.COMPANION); + map.put(PAINT_VALUES, PaintData.COMPANION); + map.put(MATRIX_RESTORE, MatrixRestore.COMPANION); + map.put(MATRIX_ROTATE, MatrixRotate.COMPANION); + map.put(MATRIX_SAVE, MatrixSave.COMPANION); + map.put(MATRIX_SCALE, MatrixScale.COMPANION); + map.put(MATRIX_SKEW, MatrixSkew.COMPANION); + map.put(MATRIX_TRANSLATE, MatrixTranslate.COMPANION); + map.put(CLIP_PATH, ClipPath.COMPANION); + map.put(CLIP_RECT, ClipRect.COMPANION); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java index 6999cdeadfd7..eece8ad52b60 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; + /** * Specify an abstract paint context used by RemoteCompose commands to draw */ @@ -30,11 +32,74 @@ public abstract class PaintContext { } public abstract void drawBitmap(int imageId, - int srcLeft, int srcTop, int srcRight, int srcBottom, - int dstLeft, int dstTop, int dstRight, int dstBottom, - int cdId); + int srcLeft, int srcTop, int srcRight, int srcBottom, + int dstLeft, int dstTop, int dstRight, int dstBottom, + int cdId); public abstract void scale(float scaleX, float scaleY); + public abstract void translate(float translateX, float translateY); + + public abstract void drawArc(float left, + float top, + float right, + float bottom, + float startAngle, + float sweepAngle); + + public abstract void drawBitmap(int id, float left, float top, float right, float bottom); + + public abstract void drawCircle(float centerX, float centerY, float radius); + + public abstract void drawLine(float x1, float y1, float x2, float y2); + + public abstract void drawOval(float left, float top, float right, float bottom); + + public abstract void drawPath(int id, float start, float end); + + public abstract void drawRect(float left, float top, float right, float bottom); + + public abstract void drawRoundRect(float left, + float top, + float right, + float bottom, + float radiusX, + float radiusY); + + public abstract void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset); + + public abstract void drawTextRun(int textID, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl); + + public abstract void drawTweenPath(int path1Id, + int path2Id, + float tween, + float start, + float stop); + + public abstract void applyPaint(PaintBundle mPaintData); + + public abstract void mtrixScale(float scaleX, float scaleY, float centerX, float centerY); + + public abstract void matrixTranslate(float translateX, float translateY); + + public abstract void matrixSkew(float skewX, float skewY); + + public abstract void matrixRotate(float rotate, float pivotX, float pivotY); + + public abstract void matrixSave(); + + public abstract void matrixRestore(); + + public abstract void clipRect(float left, float top, float right, float bottom); + + public abstract void clipPath(int pathId, int regionOp); + } diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java index abda0c0d9a0c..903dab49cd06 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java @@ -20,5 +20,8 @@ package com.android.internal.widget.remotecompose.core; */ public interface Platform { byte[] imageToByteArray(Object image); + int getImageWidth(Object image); + int getImageHeight(Object image); + float[] pathToFloatArray(Object image); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index c34730fdef04..c2e81318c09a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -17,12 +17,34 @@ package com.android.internal.widget.remotecompose.core; import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; +import com.android.internal.widget.remotecompose.core.operations.ClipPath; +import com.android.internal.widget.remotecompose.core.operations.ClipRect; +import com.android.internal.widget.remotecompose.core.operations.DrawArc; +import com.android.internal.widget.remotecompose.core.operations.DrawBitmap; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt; +import com.android.internal.widget.remotecompose.core.operations.DrawCircle; +import com.android.internal.widget.remotecompose.core.operations.DrawLine; +import com.android.internal.widget.remotecompose.core.operations.DrawOval; +import com.android.internal.widget.remotecompose.core.operations.DrawPath; +import com.android.internal.widget.remotecompose.core.operations.DrawRect; +import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect; +import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath; +import com.android.internal.widget.remotecompose.core.operations.DrawTextRun; +import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; import com.android.internal.widget.remotecompose.core.operations.Header; +import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; +import com.android.internal.widget.remotecompose.core.operations.MatrixRotate; +import com.android.internal.widget.remotecompose.core.operations.MatrixSave; +import com.android.internal.widget.remotecompose.core.operations.MatrixScale; +import com.android.internal.widget.remotecompose.core.operations.MatrixSkew; +import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; +import com.android.internal.widget.remotecompose.core.operations.PaintData; +import com.android.internal.widget.remotecompose.core.operations.PathData; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.core.operations.RootContentDescription; import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.Theme; +import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import java.io.File; import java.io.FileInputStream; @@ -82,10 +104,10 @@ public class RemoteComposeBuffer { /** * Insert a header * - * @param width the width of the document in pixels - * @param height the height of the document in pixels + * @param width the width of the document in pixels + * @param height the height of the document in pixels * @param contentDescription content description of the document - * @param capabilities bitmask indicating needed capabilities (unused for now) + * @param capabilities bitmask indicating needed capabilities (unused for now) */ public void header(int width, int height, String contentDescription, long capabilities) { Header.COMPANION.apply(mBuffer, width, height, capabilities); @@ -99,8 +121,8 @@ public class RemoteComposeBuffer { /** * Insert a header * - * @param width the width of the document in pixels - * @param height the height of the document in pixels + * @param width the width of the document in pixels + * @param height the height of the document in pixels * @param contentDescription content description of the document */ public void header(int width, int height, String contentDescription) { @@ -111,7 +133,7 @@ public class RemoteComposeBuffer { * Insert a bitmap * * @param image an opaque image that we'll add to the buffer - * @param imageWidth the width of the image + * @param imageWidth the width of the image * @param imageHeight the height of the image * @param srcLeft left coordinate of the source area * @param srcTop top coordinate of the source area @@ -161,13 +183,13 @@ public class RemoteComposeBuffer { /** * Add a click area to the document * - * @param id the id of the click area, reported in the click listener callback + * @param id the id of the click area, reported in the click listener callback * @param contentDescription the content description of that click area (accessibility) - * @param left left coordinate of the area bounds - * @param top top coordinate of the area bounds - * @param right right coordinate of the area bounds - * @param bottom bottom coordinate of the area bounds - * @param metadata associated metadata, user-provided + * @param left left coordinate of the area bounds + * @param top top coordinate of the area bounds + * @param right right coordinate of the area bounds + * @param bottom bottom coordinate of the area bounds + * @param metadata associated metadata, user-provided */ public void addClickArea( int id, @@ -193,35 +215,294 @@ public class RemoteComposeBuffer { /** * Sets the way the player handles the content * - * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) + * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END) - * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE) - * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes - * the LAYOUT modes are: - * - LAYOUT_MATCH_PARENT - * - LAYOUT_WRAP_CONTENT - * or adding an horizontal mode and a vertical mode: - * - LAYOUT_HORIZONTAL_MATCH_PARENT - * - LAYOUT_HORIZONTAL_WRAP_CONTENT - * - LAYOUT_HORIZONTAL_FIXED - * - LAYOUT_VERTICAL_MATCH_PARENT - * - LAYOUT_VERTICAL_WRAP_CONTENT - * - LAYOUT_VERTICAL_FIXED - * The LAYOUT_*_FIXED modes will use the intrinsic document size + * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE) + * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes + * the LAYOUT modes are: + * - LAYOUT_MATCH_PARENT + * - LAYOUT_WRAP_CONTENT + * or adding an horizontal mode and a vertical mode: + * - LAYOUT_HORIZONTAL_MATCH_PARENT + * - LAYOUT_HORIZONTAL_WRAP_CONTENT + * - LAYOUT_HORIZONTAL_FIXED + * - LAYOUT_VERTICAL_MATCH_PARENT + * - LAYOUT_VERTICAL_WRAP_CONTENT + * - LAYOUT_VERTICAL_FIXED + * The LAYOUT_*_FIXED modes will use the intrinsic document size */ public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) { RootContentBehavior.COMPANION.apply(mBuffer, scroll, alignment, sizing, mode); } + /** + * add Drawing the specified arc, which will be scaled to fit inside the specified oval. + * <br> + * If the start angle is negative or >= 360, the start angle is treated as start angle modulo + * 360. + * <br> + * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs + * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is + * negative, the sweep angle is treated as sweep angle modulo 360 + * <br> + * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0 + * degrees (3 o'clock on a watch.) + * <br> + * + * @param left left coordinate of oval used to define the shape and size of the arc + * @param top top coordinate of oval used to define the shape and size of the arc + * @param right right coordinate of oval used to define the shape and size of the arc + * @param bottom bottom coordinate of oval used to define the shape and size of the arc + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise + */ + public void addDrawArc(float left, + float top, + float right, + float bottom, + float startAngle, + float sweepAngle) { + DrawArc.COMPANION.apply(mBuffer, left, top, right, bottom, startAngle, sweepAngle); + } + + /** + * @param image The bitmap to be drawn + * @param left left coordinate of rectangle that the bitmap will be to fit into + * @param top top coordinate of rectangle that the bitmap will be to fit into + * @param right right coordinate of rectangle that the bitmap will be to fit into + * @param bottom bottom coordinate of rectangle that the bitmap will be to fit into + * @param contentDescription content description of the image + */ + public void addDrawBitmap(Object image, + float left, + float top, + float right, + float bottom, + String contentDescription) { + int imageId = mRemoteComposeState.dataGetId(image); + if (imageId == -1) { + imageId = mRemoteComposeState.cache(image); + byte[] data = mPlatform.imageToByteArray(image); + int imageWidth = mPlatform.getImageWidth(image); + int imageHeight = mPlatform.getImageHeight(image); + + BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data); + } + int contentDescriptionId = 0; + if (contentDescription != null) { + contentDescriptionId = addText(contentDescription); + } + DrawBitmap.COMPANION.apply( + mBuffer, imageId, left, top, right, bottom, contentDescriptionId + ); + } + + /** + * Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be + * drawn. + * + * @param centerX The x-coordinate of the center of the circle to be drawn + * @param centerY The y-coordinate of the center of the circle to be drawn + * @param radius The radius of the circle to be drawn + */ + public void addDrawCircle(float centerX, float centerY, float radius) { + DrawCircle.COMPANION.apply(mBuffer, centerX, centerY, radius); + } + + /** + * Draw a line segment with the specified start and stop x,y coordinates, using the specified + * paint. + * + * @param x1 The x-coordinate of the start point of the line + * @param y1 The y-coordinate of the start point of the line + * @param x2 The x-coordinate of the end point of the line + * @param y2 The y-coordinate of the end point of the line + */ + public void addDrawLine(float x1, float y1, float x2, float y2) { + DrawLine.COMPANION.apply(mBuffer, x1, y1, x2, y2); + } + + /** + * Draw the specified oval using the specified paint. + * + * @param left left coordinate of oval + * @param top top coordinate of oval + * @param right right coordinate of oval + * @param bottom bottom coordinate of oval + */ + public void addDrawOval(float left, float top, float right, float bottom) { + DrawOval.COMPANION.apply(mBuffer, left, top, right, bottom); + } + + /** + * Draw the specified path + * <p> + * Note: path objects are not immutable + * modifying them and calling this will not change the drawing + * + * @param path The path to be drawn + */ + public void addDrawPath(Object path) { + int id = mRemoteComposeState.dataGetId(path); + if (id == -1) { // never been seen before + id = addPathData(path); + } + addDrawPath(id); + } + + + /** + * Draw the specified path + * + * @param pathId + */ + public void addDrawPath(int pathId) { + DrawPath.COMPANION.apply(mBuffer, pathId); + } + + /** + * Draw the specified Rect + * + * @param left left coordinate of rectangle to be drawn + * @param top top coordinate of rectangle to be drawn + * @param right right coordinate of rectangle to be drawn + * @param bottom bottom coordinate of rectangle to be drawn + */ + public void addDrawRect(float left, float top, float right, float bottom) { + DrawRect.COMPANION.apply(mBuffer, left, top, right, bottom); + } + + /** + * Draw the specified round-rect + * + * @param left left coordinate of rectangle to be drawn + * @param top left coordinate of rectangle to be drawn + * @param right left coordinate of rectangle to be drawn + * @param bottom left coordinate of rectangle to be drawn + * @param radiusX The x-radius of the oval used to round the corners + * @param radiusY The y-radius of the oval used to round the corners + */ + public void addDrawRoundRect(float left, float top, float right, float bottom, + float radiusX, float radiusY) { + DrawRoundRect.COMPANION.apply(mBuffer, left, top, right, bottom, radiusX, radiusY); + } + + /** + * Draw the text, with origin at (x,y) along the specified path. + * + * @param text The text to be drawn + * @param path The path the text should follow for its baseline + * @param hOffset The distance along the path to add to the text's starting position + * @param vOffset The distance above(-) or below(+) the path to position the text + */ + public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) { + int pathId = mRemoteComposeState.dataGetId(path); + if (pathId == -1) { // never been seen before + pathId = addPathData(path); + } + int textId = addText(text); + DrawTextOnPath.COMPANION.apply(mBuffer, textId, pathId, hOffset, vOffset); + } + + /** + * Draw the text, with origin at (x,y). The origin is interpreted + * based on the Align setting in the paint. + * + * @param text The text to be drawn + * @param start The index of the first character in text to draw + * @param end (end - 1) is the index of the last character in text to draw + * @param contextStart + * @param contextEnd + * @param x The x-coordinate of the origin of the text being drawn + * @param y The y-coordinate of the baseline of the text being drawn + * @param rtl Draw RTTL + */ + public void addDrawTextRun(String text, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + int textId = addText(text); + DrawTextRun.COMPANION.apply( + mBuffer, textId, start, end, + contextStart, contextEnd, x, y, rtl); + } + + /** + * draw an interpolation between two paths that have the same pattern + * <p> + * Warning paths objects are not immutable and this is not taken into consideration + * + * @param path1 The path1 to be drawn between + * @param path2 The path2 to be drawn between + * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2 + * @param start The start of the subrange of paths to draw 0 = start form start 0.5 is half way + * @param stop The end of the subrange of paths to draw 1 = end at the end 0.5 is end half way + */ + public void addDrawTweenPath(Object path1, + Object path2, + float tween, + float start, + float stop) { + int path1Id = mRemoteComposeState.dataGetId(path1); + if (path1Id == -1) { // never been seen before + path1Id = addPathData(path1); + } + int path2Id = mRemoteComposeState.dataGetId(path2); + if (path2Id == -1) { // never been seen before + path2Id = addPathData(path2); + } + addDrawTweenPath(path1Id, path2Id, tween, start, stop); + } + + /** + * draw an interpolation between two paths that have the same pattern + * + * @param path1Id The path1 to be drawn between + * @param path2Id The path2 to be drawn between + * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2 + * @param start The start of the subrange of paths to draw 0 = start form start .5 is 1/2 way + * @param stop The end of the subrange of paths to draw 1 = end at the end .5 is end 1/2 way + */ + public void addDrawTweenPath(int path1Id, + int path2Id, + float tween, + float start, + float stop) { + DrawTweenPath.COMPANION.apply( + mBuffer, path1Id, path2Id, + tween, start, stop); + } + + /** + * Add a path object + * + * @param path + * @return the id of the path on the wire + */ + public int addPathData(Object path) { + float[] pathData = mPlatform.pathToFloatArray(path); + int id = mRemoteComposeState.cache(path); + PathData.COMPANION.apply(mBuffer, id, pathData); + return id; + } + + public void addPaint(PaintBundle paint) { + PaintData.COMPANION.apply(mBuffer, paint); + } /////////////////////////////////////////////////////////////////////////////////////////////// public void inflateFromBuffer(ArrayList<Operation> operations) { mBuffer.setIndex(0); while (mBuffer.available()) { int opId = mBuffer.readByte(); + System.out.println(">>> " + opId); CompanionOperation operation = Operations.map.get(opId); if (operation == null) { - throw new RuntimeException("Unknown operation encountered"); + throw new RuntimeException("Unknown operation encountered " + opId); } operation.read(mBuffer, operations); } @@ -259,7 +540,7 @@ public class RemoteComposeBuffer { } public static RemoteComposeBuffer fromInputStream(InputStream inputStream, - RemoteComposeState remoteComposeState) { + RemoteComposeState remoteComposeState) { RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState); read(inputStream, buffer); return buffer; @@ -318,5 +599,86 @@ public class RemoteComposeBuffer { } } + /** + * add a Pre-concat the current matrix with the specified skew. + * + * @param skewX The amount to skew in X + * @param skewY The amount to skew in Y + */ + public void addMatrixSkew(float skewX, float skewY) { + MatrixSkew.COMPANION.apply(mBuffer, skewX, skewY); + } + + /** + * This call balances a previous call to save(), and is used to remove all + * modifications to the matrix/clip state since the last save call. + * Do not call restore() more times than save() was called. + */ + public void addMatrixRestore() { + MatrixRestore.COMPANION.apply(mBuffer); + } + + /** + * Add a saves the current matrix and clip onto a private stack. + * <p> + * Subsequent calls to translate,scale,rotate,skew,concat or clipRect, + * clipPath will all operate as usual, but when the balancing call to + * restore() is made, those calls will be forgotten, and the settings that + * existed before the save() will be reinstated. + */ + public void addMatrixSave() { + MatrixSave.COMPANION.apply(mBuffer); + } + + /** + * add a pre-concat the current matrix with the specified rotation. + * + * @param angle The amount to rotate, in degrees + * @param centerX The x-coord for the pivot point (unchanged by the rotation) + * @param centerY The y-coord for the pivot point (unchanged by the rotation) + */ + public void addMatrixRotate(float angle, float centerX, float centerY) { + MatrixRotate.COMPANION.apply(mBuffer, angle, centerX, centerY); + } + + /** + * add a Pre-concat to the current matrix with the specified translation + * + * @param dx The distance to translate in X + * @param dy The distance to translate in Y + */ + public void addMatrixTranslate(float dx, float dy) { + MatrixTranslate.COMPANION.apply(mBuffer, dx, dy); + } + + /** + * Add a pre-concat of the current matrix with the specified scale. + * + * @param scaleX The amount to scale in X + * @param scaleY The amount to scale in Y + */ + public void addMatrixScale(float scaleX, float scaleY) { + MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, Float.NaN, Float.NaN); + } + + /** + * Add a pre-concat of the current matrix with the specified scale. + * + * @param scaleX The amount to scale in X + * @param scaleY The amount to scale in Y + * @param centerX The x-coord for the pivot point (unchanged by the scale) + * @param centerY The y-coord for the pivot point (unchanged by the scale) + */ + public void addMatrixScale(float scaleX, float scaleY, float centerX, float centerY) { + MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, centerX, centerY); + } + + public void addClipPath(int pathId) { + ClipPath.COMPANION.apply(mBuffer, pathId); + } + + public void addClipRect(float left, float top, float right, float bottom) { + ClipRect.COMPANION.apply(mBuffer, left, top, right, bottom); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index 1b7c6fd0f218..d16cbc5a1a16 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -37,6 +37,8 @@ public abstract class RemoteContext { public float mWidth = 0f; public float mHeight = 0f; + public abstract void loadPathData(int instanceId, float[] floatPath); + /** * The context can be used in a few different mode, allowing operations to skip being executed: * - UNSET : all operations will get executed diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java index 7c9fda5c6631..fc3202e2160d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java @@ -37,7 +37,7 @@ public class WireBuffer { this(BUFFER_SIZE); } - public void resize(int need) { + private void resize(int need) { if (mSize + need >= mMaxSize) { mMaxSize = Math.max(mMaxSize * 2, mSize + need); mBuffer = Arrays.copyOf(mBuffer, mMaxSize); @@ -120,7 +120,7 @@ public class WireBuffer { } public int readByte() { - byte value = mBuffer[mIndex]; + int value = 0xFF & mBuffer[mIndex]; mIndex++; return value; } @@ -130,6 +130,14 @@ public class WireBuffer { int v2 = (mBuffer[mIndex++] & 0xFF) << 0; return v1 + v2; } + public int peekInt() { + int tmp = mIndex; + int v1 = (mBuffer[tmp++] & 0xFF) << 24; + int v2 = (mBuffer[tmp++] & 0xFF) << 16; + int v3 = (mBuffer[tmp++] & 0xFF) << 8; + int v4 = (mBuffer[tmp++] & 0xFF) << 0; + return v1 + v2 + v3 + v4; + } public int readInt() { int v1 = (mBuffer[mIndex++] & 0xFF) << 24; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java new file mode 100644 index 000000000000..8d4a787148ef --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java @@ -0,0 +1,97 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class ClipPath extends PaintOperation { + public static final Companion COMPANION = new Companion(); + int mId; + int mRegionOp; + + public ClipPath(int pathId, int regionOp) { + mId = pathId; + mRegionOp = regionOp; + } + + public static final int REPLACE = Companion.PATH_CLIP_REPLACE; + public static final int DIFFERENCE = Companion.PATH_CLIP_DIFFERENCE; + public static final int INTERSECT = Companion.PATH_CLIP_INTERSECT; + public static final int UNION = Companion.PATH_CLIP_UNION; + public static final int XOR = Companion.PATH_CLIP_XOR; + public static final int REVERSE_DIFFERENCE = Companion.PATH_CLIP_REVERSE_DIFFERENCE; + public static final int UNDEFINED = Companion.PATH_CLIP_UNDEFINED; + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId); + } + + @Override + public String toString() { + return "ClipPath " + mId + ";"; + } + + public static class Companion implements CompanionOperation { + public static final int PATH_CLIP_REPLACE = 0; + public static final int PATH_CLIP_DIFFERENCE = 1; + public static final int PATH_CLIP_INTERSECT = 2; + public static final int PATH_CLIP_UNION = 3; + public static final int PATH_CLIP_XOR = 4; + public static final int PATH_CLIP_REVERSE_DIFFERENCE = 5; + public static final int PATH_CLIP_UNDEFINED = 6; + + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int pack = buffer.readInt(); + int id = pack & 0xFFFFF; + int regionOp = pack >> 24; + ClipPath op = new ClipPath(id, regionOp); + operations.add(op); + } + + @Override + public String name() { + return "ClipPath"; + } + + @Override + public int id() { + return Operations.CLIP_PATH; + } + + public void apply(WireBuffer buffer, int id) { + buffer.start(Operations.CLIP_PATH); + buffer.writeInt(id); + } + } + + @Override + public void paint(PaintContext context) { + context.clipPath(mId, mRegionOp); + } +} + diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java new file mode 100644 index 000000000000..803618a91737 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java @@ -0,0 +1,102 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class ClipRect extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mLeft; + float mTop; + float mRight; + float mBottom; + + public ClipRect( + float left, + float top, + float right, + float bottom) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom); + } + + @Override + public String toString() { + return "ClipRect " + mLeft + " " + mTop + + " " + mRight + " " + mBottom + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + + ClipRect op = new ClipRect(sLeft, srcTop, srcRight, srcBottom); + operations.add(op); + } + + @Override + public String name() { + return "ClipRect"; + } + + @Override + public int id() { + return Operations.CLIP_RECT; + } + + public void apply(WireBuffer buffer, + float left, + float top, + float right, + float bottom) { + buffer.start(Operations.CLIP_RECT); + buffer.writeFloat(left); + buffer.writeFloat(top); + buffer.writeFloat(right); + buffer.writeFloat(bottom); + } + } + + @Override + public void paint(PaintContext context) { + context.clipRect(mLeft, + mTop, + mRight, + mBottom); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java new file mode 100644 index 000000000000..e829975cd39b --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java @@ -0,0 +1,121 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawArc extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mLeft; + float mTop; + float mRight; + float mBottom; + float mStartAngle; + float mSweepAngle; + + public DrawArc( + float left, + float top, + float right, + float bottom, + float startAngle, + float sweepAngle) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + mStartAngle = startAngle; + mSweepAngle = sweepAngle; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mLeft, + mTop, + mRight, + mBottom, + mStartAngle, + mSweepAngle); + } + + @Override + public String toString() { + return "DrawArc " + mLeft + " " + mTop + + " " + mRight + " " + mBottom + " " + + "- " + mStartAngle + " " + mSweepAngle + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + float mStartAngle = buffer.readFloat(); + float mSweepAngle = buffer.readFloat(); + DrawArc op = new DrawArc(sLeft, srcTop, srcRight, srcBottom, + mStartAngle, mSweepAngle); + operations.add(op); + } + + @Override + public String name() { + return "DrawArc"; + } + + @Override + public int id() { + return Operations.DRAW_ARC; + } + + public void apply(WireBuffer buffer, + float left, + float top, + float right, + float bottom, + float startAngle, + float sweepAngle) { + buffer.start(Operations.DRAW_ARC); + buffer.writeFloat(left); + buffer.writeFloat(top); + buffer.writeFloat(right); + buffer.writeFloat(bottom); + buffer.writeFloat(startAngle); + buffer.writeFloat(sweepAngle); + } + } + + @Override + public void paint(PaintContext context) { + context.drawArc(mLeft, + mTop, + mRight, + mBottom, + mStartAngle, + mSweepAngle); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java new file mode 100644 index 000000000000..2e971f533ed2 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java @@ -0,0 +1,113 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawBitmap extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mLeft; + float mTop; + float mRight; + float mBottom; + int mId; + int mDescriptionId = 0; + + public DrawBitmap( + int imageId, + float left, + float top, + float right, + float bottom, + int descriptionId) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + mId = imageId; + mDescriptionId = descriptionId; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId); + } + + @Override + public String toString() { + return "DrawBitmap (desc=" + mDescriptionId + ")" + mLeft + " " + mTop + + " " + mRight + " " + mBottom + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + int discriptionId = buffer.readInt(); + + DrawBitmap op = new DrawBitmap(id, sLeft, srcTop, srcRight, srcBottom, discriptionId); + operations.add(op); + } + + @Override + public String name() { + return "DrawOval"; + } + + @Override + public int id() { + return Operations.DRAW_BITMAP; + } + + public void apply(WireBuffer buffer, + int id, + float left, + float top, + float right, + float bottom, + int descriptionId) { + buffer.start(Operations.DRAW_BITMAP); + buffer.writeInt(id); + buffer.writeFloat(left); + buffer.writeFloat(top); + buffer.writeFloat(right); + buffer.writeFloat(bottom); + buffer.writeInt(descriptionId); + } + } + + @Override + public void paint(PaintContext context) { + context.drawBitmap(mId, mLeft, + mTop, + mRight, + mBottom); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java index 3fbdf94427d1..c2a56e7256d6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java @@ -76,7 +76,8 @@ public class DrawBitmapInt extends PaintOperation { } public static class Companion implements CompanionOperation { - private Companion() {} + private Companion() { + } @Override public String name() { @@ -89,9 +90,9 @@ public class DrawBitmapInt extends PaintOperation { } public void apply(WireBuffer buffer, int imageId, - int srcLeft, int srcTop, int srcRight, int srcBottom, - int dstLeft, int dstTop, int dstRight, int dstBottom, - int cdId) { + int srcLeft, int srcTop, int srcRight, int srcBottom, + int dstLeft, int dstTop, int dstRight, int dstBottom, + int cdId) { buffer.start(Operations.DRAW_BITMAP_INT); buffer.writeInt(imageId); buffer.writeInt(srcLeft); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java new file mode 100644 index 000000000000..9ce754da1b1b --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java @@ -0,0 +1,89 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawCircle extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mCenterX; + float mCenterY; + float mRadius; + + public DrawCircle(float centerX, float centerY, float radius) { + mCenterX = centerX; + mCenterY = centerY; + mRadius = radius; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mCenterX, + mCenterY, + mRadius); + } + + @Override + public String toString() { + return ""; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float centerX = buffer.readFloat(); + float centerY = buffer.readFloat(); + float radius = buffer.readFloat(); + + DrawCircle op = new DrawCircle(centerX, centerY, radius); + operations.add(op); + } + + @Override + public String name() { + return ""; + } + + @Override + public int id() { + return 0; + } + + public void apply(WireBuffer buffer, float centerX, float centerY, float radius) { + buffer.start(Operations.DRAW_CIRCLE); + buffer.writeFloat(centerX); + buffer.writeFloat(centerY); + buffer.writeFloat(radius); + } + } + + @Override + public void paint(PaintContext context) { + context.drawCircle(mCenterX, + mCenterY, + mRadius); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java new file mode 100644 index 000000000000..c7a8315a2274 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java @@ -0,0 +1,105 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawLine extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mX1; + float mY1; + float mX2; + float mY2; + + public DrawLine( + float x1, + float y1, + float x2, + float y2) { + mX1 = x1; + mY1 = y1; + mX2 = x2; + mY2 = y2; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mX1, + mY1, + mX2, + mY2); + } + + @Override + public String toString() { + return "DrawArc " + mX1 + " " + mY1 + + " " + mX2 + " " + mY2 + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float x1 = buffer.readFloat(); + float y1 = buffer.readFloat(); + float x2 = buffer.readFloat(); + float y2 = buffer.readFloat(); + + DrawLine op = new DrawLine(x1, y1, x2, y2); + operations.add(op); + } + + @Override + public String name() { + return "DrawLine"; + } + + @Override + public int id() { + return Operations.DRAW_LINE; + } + + public void apply(WireBuffer buffer, + float x1, + float y1, + float x2, + float y2) { + buffer.start(Operations.DRAW_LINE); + buffer.writeFloat(x1); + buffer.writeFloat(y1); + buffer.writeFloat(x2); + buffer.writeFloat(y2); + } + } + + @Override + public void paint(PaintContext context) { + context.drawLine(mX1, + mY1, + mX2, + mY2); + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java new file mode 100644 index 000000000000..714375335cb2 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java @@ -0,0 +1,102 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawOval extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mLeft; + float mTop; + float mRight; + float mBottom; + + + public DrawOval( + float left, + float top, + float right, + float bottom) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom); + } + + @Override + public String toString() { + return "DrawOval " + mLeft + " " + mTop + + " " + mRight + " " + mBottom + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + + DrawOval op = new DrawOval(sLeft, srcTop, srcRight, srcBottom); + operations.add(op); + } + + @Override + public String name() { + return "DrawOval"; + } + + @Override + public int id() { + return Operations.DRAW_OVAL; + } + + public void apply(WireBuffer buffer, + float left, + float top, + float right, + float bottom) { + buffer.start(Operations.DRAW_OVAL); + buffer.writeFloat(left); + buffer.writeFloat(top); + buffer.writeFloat(right); + buffer.writeFloat(bottom); + } + } + + @Override + public void paint(PaintContext context) { + context.drawOval(mLeft, + mTop, + mRight, + mBottom); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java new file mode 100644 index 000000000000..7b8a9e95d9cb --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java @@ -0,0 +1,78 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawPath extends PaintOperation { + public static final Companion COMPANION = new Companion(); + int mId; + float mStart = 0; + float mEnd = 1; + + public DrawPath(int pathId) { + mId = pathId; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId); + } + + @Override + public String toString() { + return "DrawPath " + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + DrawPath op = new DrawPath(id); + operations.add(op); + } + + @Override + public String name() { + return "DrawPath"; + } + + @Override + public int id() { + return Operations.DRAW_PATH; + } + + public void apply(WireBuffer buffer, int id) { + buffer.start(Operations.DRAW_PATH); + buffer.writeInt(id); + } + } + + @Override + public void paint(PaintContext context) { + context.drawPath(mId, mStart, mEnd); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java new file mode 100644 index 000000000000..4775241faa6f --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java @@ -0,0 +1,102 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawRect extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mLeft; + float mTop; + float mRight; + float mBottom; + + public DrawRect( + float left, + float top, + float right, + float bottom) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom); + } + + @Override + public String toString() { + return "DrawRect " + mLeft + " " + mTop + + " " + mRight + " " + mBottom + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + + DrawRect op = new DrawRect(sLeft, srcTop, srcRight, srcBottom); + operations.add(op); + } + + @Override + public String name() { + return "DrawRect"; + } + + @Override + public int id() { + return Operations.DRAW_RECT; + } + + public void apply(WireBuffer buffer, + float left, + float top, + float right, + float bottom) { + buffer.start(Operations.DRAW_RECT); + buffer.writeFloat(left); + buffer.writeFloat(top); + buffer.writeFloat(right); + buffer.writeFloat(bottom); + } + } + + @Override + public void paint(PaintContext context) { + context.drawRect(mLeft, + mTop, + mRight, + mBottom); + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java new file mode 100644 index 000000000000..8da16e768b7f --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java @@ -0,0 +1,119 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawRoundRect extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mLeft; + float mTop; + float mRight; + float mBottom; + float mRadiusX; + float mRadiusY; + + public DrawRoundRect( + float left, + float top, + float right, + float bottom, + float radiusX, + float radiusY) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + mRadiusX = radiusX; + mRadiusY = radiusY; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom, mRadiusX, mRadiusY); + } + + @Override + public String toString() { + return "DrawRoundRect " + mLeft + " " + mTop + + " " + mRight + " " + mBottom + + " (" + mRadiusX + " " + mRadiusY + ");"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + float srcRadiusX = buffer.readFloat(); + float srcRadiusY = buffer.readFloat(); + + DrawRoundRect op = new DrawRoundRect(sLeft, srcTop, srcRight, + srcBottom, srcRadiusX, srcRadiusY); + operations.add(op); + } + + @Override + public String name() { + return "DrawOval"; + } + + @Override + public int id() { + return Operations.DRAW_ROUND_RECT; + } + + public void apply(WireBuffer buffer, + float left, + float top, + float right, + float bottom, + float radiusX, + float radiusY) { + buffer.start(Operations.DRAW_ROUND_RECT); + buffer.writeFloat(left); + buffer.writeFloat(top); + buffer.writeFloat(right); + buffer.writeFloat(bottom); + buffer.writeFloat(radiusX); + buffer.writeFloat(radiusY); + } + } + + @Override + public void paint(PaintContext context) { + context.drawRoundRect(mLeft, + mTop, + mRight, + mBottom, + mRadiusX, + mRadiusY + ); + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java new file mode 100644 index 000000000000..1856e3097ec0 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java @@ -0,0 +1,88 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawTextOnPath extends PaintOperation { + public static final Companion COMPANION = new Companion(); + int mPathId; + public int mTextId; + float mVOffset; + float mHOffset; + + public DrawTextOnPath(int textId, int pathId, float hOffset, float vOffset) { + mPathId = pathId; + mTextId = textId; + mHOffset = vOffset; + mVOffset = hOffset; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextId, mPathId, mHOffset, mVOffset); + } + + @Override + public String toString() { + return "DrawTextOnPath " + " " + mPathId + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int textId = buffer.readInt(); + int pathId = buffer.readInt(); + float hOffset = buffer.readFloat(); + float vOffset = buffer.readFloat(); + DrawTextOnPath op = new DrawTextOnPath(textId, pathId, hOffset, vOffset); + operations.add(op); + } + + @Override + public String name() { + return "DrawTextOnPath"; + } + + @Override + public int id() { + return Operations.DRAW_TEXT_ON_PATH; + } + + public void apply(WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) { + buffer.start(Operations.DRAW_TEXT_ON_PATH); + buffer.writeInt(textId); + buffer.writeInt(pathId); + buffer.writeFloat(hOffset); + buffer.writeFloat(vOffset); + } + } + + @Override + public void paint(PaintContext context) { + context.drawTextOnPath(mTextId, mPathId, mHOffset, mVOffset); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java new file mode 100644 index 000000000000..a0992528d981 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java @@ -0,0 +1,121 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawTextRun extends PaintOperation { + public static final Companion COMPANION = new Companion(); + int mTextID; + int mStart = 0; + int mEnd = 0; + int mContextStart = 0; + int mContextEnd = 0; + float mX = 0f; + float mY = 0f; + boolean mRtl = false; + + public DrawTextRun(int textID, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + mTextID = textID; + mStart = start; + mEnd = end; + mContextStart = contextStart; + mContextEnd = contextEnd; + mX = x; + mY = y; + mRtl = rtl; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl); + + } + + @Override + public String toString() { + return ""; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int text = buffer.readInt(); + int start = buffer.readInt(); + int end = buffer.readInt(); + int contextStart = buffer.readInt(); + int contextEnd = buffer.readInt(); + float x = buffer.readFloat(); + float y = buffer.readFloat(); + boolean rtl = buffer.readBoolean(); + DrawTextRun op = new DrawTextRun(text, start, end, contextStart, contextEnd, x, y, rtl); + + operations.add(op); + } + + @Override + public String name() { + return ""; + } + + @Override + public int id() { + return 0; + } + + public void apply(WireBuffer buffer, + int textID, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + buffer.start(Operations.DRAW_TEXT_RUN); + buffer.writeInt(textID); + buffer.writeInt(start); + buffer.writeInt(end); + buffer.writeInt(contextStart); + buffer.writeInt(contextEnd); + buffer.writeFloat(x); + buffer.writeFloat(y); + buffer.writeBoolean(rtl); + } + } + + @Override + public void paint(PaintContext context) { + context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java new file mode 100644 index 000000000000..ef0a4ad2eff3 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java @@ -0,0 +1,114 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class DrawTweenPath extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mTween; + float mStart; + float mStop; + int mPath1Id; + int mPath2Id; + + public DrawTweenPath( + int path1Id, + int path2Id, + float tween, + float start, + float stop) { + mTween = tween; + mStart = start; + mStop = stop; + mPath1Id = path1Id; + mPath2Id = path2Id; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mPath1Id, + mPath2Id, + mTween, + mStart, + mStop); + } + + @Override + public String toString() { + return "DrawTweenPath " + mPath1Id + " " + mPath2Id + + " " + mTween + " " + mStart + " " + + "- " + mStop + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int path1Id = buffer.readInt(); + int path2Id = buffer.readInt(); + float tween = buffer.readFloat(); + float start = buffer.readFloat(); + float stop = buffer.readFloat(); + DrawTweenPath op = new DrawTweenPath(path1Id, path2Id, + tween, start, stop); + operations.add(op); + } + + @Override + public String name() { + return "DrawTweenPath"; + } + + @Override + public int id() { + return Operations.DRAW_TWEEN_PATH; + } + + public void apply(WireBuffer buffer, + int path1Id, + int path2Id, + float tween, + float start, + float stop) { + buffer.start(Operations.DRAW_TWEEN_PATH); + buffer.writeInt(path1Id); + buffer.writeInt(path2Id); + buffer.writeFloat(tween); + buffer.writeFloat(start); + buffer.writeFloat(stop); + } + } + + @Override + public void paint(PaintContext context) { + context.drawTweenPath(mPath1Id, + mPath2Id, + mTween, + mStart, + mStop); + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java index eca43c5e3281..aabed15e833d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java @@ -26,11 +26,11 @@ import java.util.List; /** * Describe some basic information for a RemoteCompose document - * + * <p> * It encodes the version of the document (following semantic versioning) as well * as the dimensions of the document in pixels. */ -public class Header implements RemoteComposeOperation { +public class Header implements RemoteComposeOperation { public static final int MAJOR_VERSION = 0; public static final int MINOR_VERSION = 1; public static final int PATCH_VERSION = 0; @@ -89,7 +89,8 @@ public class Header implements RemoteComposeOperation { } public static class Companion implements CompanionOperation { - private Companion() {} + private Companion() { + } @Override public String name() { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java new file mode 100644 index 000000000000..482e0e22bd57 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java @@ -0,0 +1,73 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class MatrixRestore extends PaintOperation { + public static final Companion COMPANION = new Companion(); + + public MatrixRestore() { + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer); + } + + @Override + public String toString() { + return "MatrixRestore;"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + + MatrixRestore op = new MatrixRestore(); + operations.add(op); + } + + @Override + public String name() { + return "MatrixRestore"; + } + + @Override + public int id() { + return Operations.MATRIX_RESTORE; + } + + public void apply(WireBuffer buffer) { + buffer.start(Operations.MATRIX_RESTORE); + } + } + + @Override + public void paint(PaintContext context) { + context.matrixRestore(); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java new file mode 100644 index 000000000000..d6c89e0d2c64 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java @@ -0,0 +1,82 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class MatrixRotate extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mRotate, mPivotX, mPivotY; + + public MatrixRotate(float rotate, float pivotX, float pivotY) { + mRotate = rotate; + mPivotX = pivotX; + mPivotY = pivotY; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mRotate, mPivotX, mPivotY); + } + + @Override + public String toString() { + return "DrawArc " + mRotate + ", " + mPivotX + ", " + mPivotY + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float rotate = buffer.readFloat(); + float pivotX = buffer.readFloat(); + float pivotY = buffer.readFloat(); + MatrixRotate op = new MatrixRotate(rotate, pivotX, pivotY); + operations.add(op); + } + + @Override + public String name() { + return "Matrix"; + } + + @Override + public int id() { + return Operations.MATRIX_ROTATE; + } + + public void apply(WireBuffer buffer, float rotate, float pivotX, float pivotY) { + buffer.start(Operations.MATRIX_ROTATE); + buffer.writeFloat(rotate); + buffer.writeFloat(pivotX); + buffer.writeFloat(pivotY); + } + } + + @Override + public void paint(PaintContext context) { + context.matrixRotate(mRotate, mPivotX, mPivotY); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java new file mode 100644 index 000000000000..d3d5bfba8992 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class MatrixSave extends PaintOperation { + public static final Companion COMPANION = new Companion(); + + public MatrixSave() { + + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer); + } + + @Override + public String toString() { + return "MatrixSave;"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + + MatrixSave op = new MatrixSave(); + operations.add(op); + } + + @Override + public String name() { + return "Matrix"; + } + + @Override + public int id() { + return Operations.MATRIX_SAVE; + } + + public void apply(WireBuffer buffer) { + buffer.start(Operations.MATRIX_SAVE); + } + } + + @Override + public void paint(PaintContext context) { + context.matrixSave(); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java new file mode 100644 index 000000000000..28aa68dd5884 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java @@ -0,0 +1,88 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class MatrixScale extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mScaleX, mScaleY; + float mCenterX, mCenterY; + + public MatrixScale(float scaleX, float scaleY, float centerX, float centerY) { + mScaleX = scaleX; + mScaleY = scaleY; + mCenterX = centerX; + mCenterY = centerY; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mScaleX, mScaleY, mCenterX, mCenterY); + } + + @Override + public String toString() { + return "MatrixScale " + mScaleY + ", " + mScaleY + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float scaleX = buffer.readFloat(); + float scaleY = buffer.readFloat(); + float centerX = buffer.readFloat(); + float centerY = buffer.readFloat(); + MatrixScale op = new MatrixScale(scaleX, scaleY, centerX, centerY); + operations.add(op); + } + + @Override + public String name() { + return "Matrix"; + } + + @Override + public int id() { + return Operations.MATRIX_SCALE; + } + + public void apply(WireBuffer buffer, float scaleX, float scaleY, + float centerX, float centerY) { + buffer.start(Operations.MATRIX_SCALE); + buffer.writeFloat(scaleX); + buffer.writeFloat(scaleY); + buffer.writeFloat(centerX); + buffer.writeFloat(centerY); + + } + } + + @Override + public void paint(PaintContext context) { + context.mtrixScale(mScaleX, mScaleY, mCenterX, mCenterY); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java new file mode 100644 index 000000000000..a3888997c758 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java @@ -0,0 +1,79 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class MatrixSkew extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mSkewX, mSkewY; + + public MatrixSkew(float skewX, float skewY) { + mSkewX = skewX; + mSkewY = skewY; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mSkewX, mSkewY); + } + + @Override + public String toString() { + return "DrawArc " + mSkewY + ", " + mSkewY + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float skewX = buffer.readFloat(); + float skewY = buffer.readFloat(); + MatrixSkew op = new MatrixSkew(skewX, skewY); + operations.add(op); + } + + @Override + public String name() { + return "Matrix"; + } + + @Override + public int id() { + return Operations.MATRIX_SKEW; + } + + public void apply(WireBuffer buffer, float skewX, float skewY) { + buffer.start(Operations.MATRIX_SKEW); + buffer.writeFloat(skewX); + buffer.writeFloat(skewY); + } + } + + @Override + public void paint(PaintContext context) { + context.matrixSkew(mSkewX, mSkewY); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java new file mode 100644 index 000000000000..32987521e041 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java @@ -0,0 +1,79 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class MatrixTranslate extends PaintOperation { + public static final Companion COMPANION = new Companion(); + float mTranslateX, mTranslateY; + + public MatrixTranslate(float translateX, float translateY) { + mTranslateX = translateX; + mTranslateY = translateY; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTranslateX, mTranslateY); + } + + @Override + public String toString() { + return "DrawArc " + mTranslateY + ", " + mTranslateY + ";"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float translateX = buffer.readFloat(); + float translateY = buffer.readFloat(); + MatrixTranslate op = new MatrixTranslate(translateX, translateY); + operations.add(op); + } + + @Override + public String name() { + return "Matrix"; + } + + @Override + public int id() { + return Operations.MATRIX_TRANSLATE; + } + + public void apply(WireBuffer buffer, float translateX, float translateY) { + buffer.start(Operations.MATRIX_TRANSLATE); + buffer.writeFloat(translateX); + buffer.writeFloat(translateY); + } + } + + @Override + public void paint(PaintContext context) { + context.matrixTranslate(mTranslateX, mTranslateY); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java new file mode 100644 index 000000000000..e5683ece7919 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java @@ -0,0 +1,83 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; + +import java.util.List; + +public class PaintData extends PaintOperation { + public PaintBundle mPaintData = new PaintBundle(); + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + + public PaintData() { + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mPaintData); + } + + @Override + public String toString() { + return "PaintData " + "\"" + mPaintData + "\""; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "TextData"; + } + + @Override + public int id() { + return Operations.PAINT_VALUES; + } + + public void apply(WireBuffer buffer, PaintBundle paintBundle) { + buffer.start(Operations.PAINT_VALUES); + paintBundle.writeBundle(buffer); + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + PaintData data = new PaintData(); + data.mPaintData.readBundle(buffer); + operations.add(data); + } + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } + + @Override + public void paint(PaintContext context) { + context.applyPaint(mPaintData); + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java new file mode 100644 index 000000000000..2646b27b1f51 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java @@ -0,0 +1,176 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +public class PathData implements Operation { + public static final Companion COMPANION = new Companion(); + int mInstanceId; + float[] mRef; + float[] mFloatPath; + float[] mRetFloats; + + PathData(int instanceId, float[] floatPath) { + mInstanceId = instanceId; + mFloatPath = floatPath; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mInstanceId, mFloatPath); + } + + @Override + public String deepToString(String indent) { + return pathString(mFloatPath); + } + + public float[] getFloatPath(PaintContext context) { + float[] ret = mRetFloats; // Assume retFloats is declared elsewhere + if (ret == null) { + return mFloatPath; // Assume floatPath is declared elsewhere + } + float[] localRef = mRef; // Assume ref is of type Float[] + if (localRef == null) { + for (int i = 0; i < mFloatPath.length; i++) { + ret[i] = mFloatPath[i]; + } + } else { + for (int i = 0; i < mFloatPath.length; i++) { + float lr = localRef[i]; + if (Float.isNaN(lr)) { + ret[i] = Utils.getActualValue(lr); + } else { + ret[i] = mFloatPath[i]; + } + } + } + return ret; + } + + public static final int MOVE = 10; + public static final int LINE = 11; + public static final int QUADRATIC = 12; + public static final int CONIC = 13; + public static final int CUBIC = 14; + public static final int CLOSE = 15; + public static final int DONE = 16; + public static final float MOVE_NAN = Utils.asNan(MOVE); + public static final float LINE_NAN = Utils.asNan(LINE); + public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC); + public static final float CONIC_NAN = Utils.asNan(CONIC); + public static final float CUBIC_NAN = Utils.asNan(CUBIC); + public static final float CLOSE_NAN = Utils.asNan(CLOSE); + public static final float DONE_NAN = Utils.asNan(DONE); + + public static class Companion implements CompanionOperation { + + private Companion() { + } + + @Override + public String name() { + return "BitmapData"; + } + + @Override + public int id() { + return Operations.DATA_PATH; + } + + public void apply(WireBuffer buffer, int id, float[] data) { + buffer.start(Operations.DATA_PATH); + buffer.writeInt(id); + buffer.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + buffer.writeFloat(data[i]); + } + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int imageId = buffer.readInt(); + int len = buffer.readInt(); + float[] data = new float[len]; + for (int i = 0; i < data.length; i++) { + data[i] = buffer.readFloat(); + } + operations.add(new PathData(imageId, data)); + } + } + + public static String pathString(float[] path) { + if (path == null) { + return "null"; + } + StringBuilder str = new StringBuilder(); + for (int i = 0; i < path.length; i++) { + if (i != 0) { + str.append(" "); + } + if (Float.isNaN(path[i])) { + int id = Utils.idFromNan(path[i]); // Assume idFromNan is defined elsewhere + if (id <= DONE) { // Assume DONE is a constant + switch (id) { + case MOVE: + str.append("M"); + break; + case LINE: + str.append("L"); + break; + case QUADRATIC: + str.append("Q"); + break; + case CONIC: + str.append("R"); + break; + case CUBIC: + str.append("C"); + break; + case CLOSE: + str.append("Z"); + break; + case DONE: + str.append("."); + break; + default: + str.append("X"); + break; + } + } else { + str.append("(" + id + ")"); + } + } else { + str.append(path[i]); + } + } + return str.toString(); + } + + @Override + public void apply(RemoteContext context) { + context.loadPathData(mInstanceId, mFloatPath); + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java index ad4caea7aef8..6d924eb70c50 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java @@ -28,7 +28,7 @@ import java.util.List; /** * Describe some basic information for a RemoteCompose document - * + * <p> * It encodes the version of the document (following semantic versioning) as well * as the dimensions of the document in pixels. */ @@ -100,21 +100,21 @@ public class RootContentBehavior implements RemoteComposeOperation { /** * Sets the way the player handles the content * - * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) + * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END) - * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE) - * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes - * the LAYOUT modes are: - * - LAYOUT_MATCH_PARENT - * - LAYOUT_WRAP_CONTENT - * or adding an horizontal mode and a vertical mode: - * - LAYOUT_HORIZONTAL_MATCH_PARENT - * - LAYOUT_HORIZONTAL_WRAP_CONTENT - * - LAYOUT_HORIZONTAL_FIXED - * - LAYOUT_VERTICAL_MATCH_PARENT - * - LAYOUT_VERTICAL_WRAP_CONTENT - * - LAYOUT_VERTICAL_FIXED - * The LAYOUT_*_FIXED modes will use the intrinsic document size + * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE) + * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes + * the LAYOUT modes are: + * - LAYOUT_MATCH_PARENT + * - LAYOUT_WRAP_CONTENT + * or adding an horizontal mode and a vertical mode: + * - LAYOUT_HORIZONTAL_MATCH_PARENT + * - LAYOUT_HORIZONTAL_WRAP_CONTENT + * - LAYOUT_HORIZONTAL_FIXED + * - LAYOUT_VERTICAL_MATCH_PARENT + * - LAYOUT_VERTICAL_WRAP_CONTENT + * - LAYOUT_VERTICAL_FIXED + * The LAYOUT_*_FIXED modes will use the intrinsic document size */ public RootContentBehavior(int scroll, int alignment, int sizing, int mode) { switch (scroll) { @@ -149,10 +149,12 @@ public class RootContentBehavior implements RemoteComposeOperation { switch (sizing) { case SIZING_LAYOUT: { Log.e(TAG, "sizing_layout is not yet supported"); - } break; + } + break; case SIZING_SCALE: { mSizing = sizing; - } break; + } + break; default: { Log.e(TAG, "incorrect sizing value " + sizing); } @@ -200,7 +202,8 @@ public class RootContentBehavior implements RemoteComposeOperation { } public static class Companion implements CompanionOperation { - private Companion() {} + private Companion() { + } @Override public String name() { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java new file mode 100644 index 000000000000..00e2f2058e89 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -0,0 +1,41 @@ +/* + * 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.internal.widget.remotecompose.core.operations; + +public class Utils { + public static float asNan(int v) { + return Float.intBitsToFloat(v | -0x800000); + } + + public static int idFromNan(float value) { + int b = Float.floatToRawIntBits(value); + return b & 0xFFFFF; + } + + public static float getActualValue(float lr) { + return 0; + } + + String getFloatString(float value) { + if (Float.isNaN(value)) { + int id = idFromNan(value); + if (id > 0) { + return "NaN(" + id + ")"; + } + } + return "" + value; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java new file mode 100644 index 000000000000..8abb0bfff338 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java @@ -0,0 +1,829 @@ +/* + * 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.internal.widget.remotecompose.core.operations.paint; + +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.Arrays; + +public class PaintBundle { + int[] mArray = new int[200]; + int mPos = 0; + + public void applyPaintChange(PaintChanges p) { + int i = 0; + int mask = 0; + while (i < mPos) { + int cmd = mArray[i++]; + mask = mask | (1 << (cmd - 1)); + switch (cmd & 0xFFFF) { + case TEXT_SIZE: { + p.setTextSize(Float.intBitsToFloat(mArray[i++])); + break; + } + case TYPEFACE: + int style = (cmd >> 16); + int weight = style & 0x3ff; + boolean italic = (style >> 10) > 0; + int font_type = mArray[i++]; + + p.setTypeFace(font_type, weight, italic); + break; + case COLOR: { + p.setColor(mArray[i++]); + break; + } + case STROKE_WIDTH: { + p.setStrokeWidth(Float.intBitsToFloat(mArray[i++])); + break; + } + case STROKE_MITER: { + p.setStrokeMiter(Float.intBitsToFloat(mArray[i++])); + break; + } + case STROKE_CAP: { + p.setStrokeCap(cmd >> 16); + break; + } + case STYLE: { + p.setStyle(cmd >> 16); + break; + } + case SHADER: { + break; + } + case STROKE_JOIN: { + p.setStrokeJoin(cmd >> 16); + break; + } + case IMAGE_FILTER_QUALITY: { + p.setImageFilterQuality(cmd >> 16); + break; + } + case BLEND_MODE: { + p.setBlendMode(cmd >> 16); + break; + } + case FILTER_BITMAP: { + p.setFilterBitmap(!((cmd >> 16) == 0)); + break; + } + + case GRADIENT: { + i = callSetGradient(cmd, mArray, i, p); + break; + } + case COLOR_FILTER: { + p.setColorFilter(mArray[i++], cmd >> 16); + break; + } + case ALPHA: { + p.setAlpha(Float.intBitsToFloat(mArray[i++])); + break; + } + } + } + + mask = (~mask) & PaintChanges.VALID_BITS; + + p.clear(mask); + } + + private String toName(int id) { + switch (id) { + case TEXT_SIZE: + return "TEXT_SIZE"; + + case COLOR: + return "COLOR"; + case STROKE_WIDTH: + return "STROKE_WIDTH"; + case STROKE_MITER: + return "STROKE_MITER"; + case TYPEFACE: + return "TYPEFACE"; + case STROKE_CAP: + return "CAP"; + case STYLE: + return "STYLE"; + case SHADER: + return "SHADER"; + case IMAGE_FILTER_QUALITY: + return "IMAGE_FILTER_QUALITY"; + case BLEND_MODE: + return "BLEND_MODE"; + case FILTER_BITMAP: + return "FILTER_BITMAP"; + case GRADIENT: + return "GRADIENT_LINEAR"; + case ALPHA: + return "ALPHA"; + case COLOR_FILTER: + return "COLOR_FILTER"; + + } + return "????" + id + "????"; + } + + private static String colorInt(int color) { + String str = "000000000000" + Integer.toHexString(color); + return "0x" + str.substring(str.length() - 8); + } + + private static String colorInt(int[] color) { + String str = "["; + for (int i = 0; i < color.length; i++) { + if (i > 0) { + str += ", "; + } + str += colorInt(color[i]); + } + return str + "]"; + } + + @Override + public String toString() { + StringBuilder ret = new StringBuilder("\n"); + int i = 0; + while (i < mPos) { + int cmd = mArray[i++]; + int type = cmd & 0xFFFF; + switch (type) { + + case TEXT_SIZE: { + ret.append(" TextSize(" + Float.intBitsToFloat(mArray[i++])); + } + + break; + case TYPEFACE: { + int style = (cmd >> 16); + int weight = style & 0x3ff; + boolean italic = (style >> 10) > 0; + int font_type = mArray[i++]; + ret.append(" TypeFace(" + (font_type + ", " + + weight + ", " + italic)); + } + break; + case COLOR: { + ret.append(" Color(" + colorInt(mArray[i++])); + } + break; + case STROKE_WIDTH: { + ret.append(" StrokeWidth(" + + (Float.intBitsToFloat(mArray[i++]))); + } + break; + case STROKE_MITER: { + ret.append(" StrokeMiter(" + + (Float.intBitsToFloat(mArray[i++]))); + } + break; + case STROKE_CAP: { + ret.append(" StrokeCap(" + + (cmd >> 16)); + } + break; + case STYLE: { + ret.append(" Style(" + (cmd >> 16)); + } + break; + case COLOR_FILTER: { + ret.append(" ColorFilter(color=" + + colorInt(mArray[i++]) + + ", mode=" + blendModeString(cmd >> 16)); + } + break; + case SHADER: { + } + break; + case ALPHA: { + ret.append(" Alpha(" + + (Float.intBitsToFloat(mArray[i++]))); + } + break; + case IMAGE_FILTER_QUALITY: { + ret.append(" ImageFilterQuality(" + (cmd >> 16)); + } + break; + case BLEND_MODE: { + ret.append(" BlendMode(" + blendModeString(cmd >> 16)); + } + break; + case FILTER_BITMAP: { + ret.append(" FilterBitmap(" + + (!((cmd >> 16) == 0))); + } + break; + case STROKE_JOIN: { + ret.append(" StrokeJoin(" + (cmd >> 16)); + } + break; + case ANTI_ALIAS: { + ret.append(" AntiAlias(" + (cmd >> 16)); + } + break; + case GRADIENT: { + i = callPrintGradient(cmd, mArray, i, ret); + } + } + ret.append("),\n"); + } + return ret.toString(); + } + + + int callPrintGradient(int cmd, int[] array, int i, StringBuilder p) { + int ret = i; + int type = (cmd >> 16); + switch (type) { + + case 0: { + p.append(" LinearGradient(\n"); + int len = array[ret++]; + int[] colors = null; + if (len > 0) { + colors = new int[len]; + for (int j = 0; j < colors.length; j++) { + colors[j] = array[ret++]; + + } + } + len = array[ret++]; + float[] stops = null; + if (len > 0) { + stops = new float[len]; + for (int j = 0; j < stops.length; j++) { + stops[j] = Float.intBitsToFloat(array[ret++]); + } + } + + p.append(" colors = " + colorInt(colors) + ",\n"); + p.append(" stops = " + Arrays.toString(stops) + ",\n"); + p.append(" start = "); + p.append("[" + Float.intBitsToFloat(array[ret++])); + p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n"); + p.append(" end = "); + p.append("[" + Float.intBitsToFloat(array[ret++])); + p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n"); + int tileMode = array[ret++]; + p.append(" tileMode = " + tileMode + "\n "); + } + + break; + case 1: { + p.append(" RadialGradient(\n"); + int len = array[ret++]; + int[] colors = null; + if (len > 0) { + colors = new int[len]; + for (int j = 0; j < colors.length; j++) { + colors[j] = array[ret++]; + + } + } + len = array[ret++]; + float[] stops = null; + if (len > 0) { + stops = new float[len]; + for (int j = 0; j < stops.length; j++) { + stops[j] = Float.intBitsToFloat(array[ret++]); + } + } + + p.append(" colors = " + colorInt(colors) + ",\n"); + p.append(" stops = " + Arrays.toString(stops) + ",\n"); + p.append(" center = "); + p.append("[" + Float.intBitsToFloat(array[ret++])); + p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n"); + p.append(" radius ="); + p.append(" " + Float.intBitsToFloat(array[ret++]) + ",\n"); + int tileMode = array[ret++]; + p.append(" tileMode = " + tileMode + "\n "); + } + + break; + case 2: { + p.append(" SweepGradient(\n"); + int len = array[ret++]; + int[] colors = null; + if (len > 0) { + colors = new int[len]; + for (int j = 0; j < colors.length; j++) { + colors[j] = array[ret++]; + + } + } + len = array[ret++]; + float[] stops = null; + if (len > 0) { + stops = new float[len]; + for (int j = 0; j < stops.length; j++) { + stops[j] = Float.intBitsToFloat(array[ret++]); + } + } + + p.append(" colors = " + colorInt(colors) + ",\n"); + p.append(" stops = " + Arrays.toString(stops) + ",\n"); + p.append(" center = "); + p.append("[" + Float.intBitsToFloat(array[ret++])); + p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n "); + + } + break; + default: { + p.append("GRADIENT_??????!!!!"); + } + } + + return ret; + } + + int callSetGradient(int cmd, int[] array, int i, PaintChanges p) { + int ret = i; + int gradientType = (cmd >> 16); + + int len = array[ret++]; + int[] colors = null; + if (len > 0) { + colors = new int[len]; + for (int j = 0; j < colors.length; j++) { + colors[j] = array[ret++]; + } + } + len = array[ret++]; + float[] stops = null; + if (len > 0) { + stops = new float[len]; + for (int j = 0; j < colors.length; j++) { + stops[j] = Float.intBitsToFloat(array[ret++]); + } + } + + if (colors == null) { + return ret; + } + + + switch (gradientType) { + + case LINEAR_GRADIENT: { + float startX = Float.intBitsToFloat(array[ret++]); + float startY = Float.intBitsToFloat(array[ret++]); + float endX = Float.intBitsToFloat(array[ret++]); + float endY = Float.intBitsToFloat(array[ret++]); + int tileMode = array[ret++]; + p.setLinearGradient(colors, stops, startX, + startY, endX, endY, tileMode); + } + + break; + case RADIAL_GRADIENT: { + float centerX = Float.intBitsToFloat(array[ret++]); + float centerY = Float.intBitsToFloat(array[ret++]); + float radius = Float.intBitsToFloat(array[ret++]); + int tileMode = array[ret++]; + p.setRadialGradient(colors, stops, centerX, centerY, + radius, tileMode); + } + break; + case SWEEP_GRADIENT: { + float centerX = Float.intBitsToFloat(array[ret++]); + float centerY = Float.intBitsToFloat(array[ret++]); + p.setSweepGradient(colors, stops, centerX, centerY); + } + } + + return ret; + } + + public void writeBundle(WireBuffer buffer) { + buffer.writeInt(mPos); + for (int index = 0; index < mPos; index++) { + buffer.writeInt(mArray[index]); + } + } + + public void readBundle(WireBuffer buffer) { + int len = buffer.readInt(); + if (len <= 0 || len > 1024) { + throw new RuntimeException("buffer corrupt paint len = " + len); + } + mArray = new int[len]; + for (int i = 0; i < mArray.length; i++) { + mArray[i] = buffer.readInt(); + } + mPos = len; + } + + public static final int TEXT_SIZE = 1; // float + + public static final int COLOR = 4; // int + public static final int STROKE_WIDTH = 5; // float + public static final int STROKE_MITER = 6; + public static final int STROKE_CAP = 7; // int + public static final int STYLE = 8; // int + public static final int SHADER = 9; // int + public static final int IMAGE_FILTER_QUALITY = 10; // int + public static final int GRADIENT = 11; + public static final int ALPHA = 12; + public static final int COLOR_FILTER = 13; + public static final int ANTI_ALIAS = 14; + public static final int STROKE_JOIN = 15; + public static final int TYPEFACE = 16; + public static final int FILTER_BITMAP = 17; + public static final int BLEND_MODE = 18; + + + public static final int BLEND_MODE_CLEAR = 0; + public static final int BLEND_MODE_SRC = 1; + public static final int BLEND_MODE_DST = 2; + public static final int BLEND_MODE_SRC_OVER = 3; + public static final int BLEND_MODE_DST_OVER = 4; + public static final int BLEND_MODE_SRC_IN = 5; + public static final int BLEND_MODE_DST_IN = 6; + public static final int BLEND_MODE_SRC_OUT = 7; + public static final int BLEND_MODE_DST_OUT = 8; + public static final int BLEND_MODE_SRC_ATOP = 9; + public static final int BLEND_MODE_DST_ATOP = 10; + public static final int BLEND_MODE_XOR = 11; + public static final int BLEND_MODE_PLUS = 12; + public static final int BLEND_MODE_MODULATE = 13; + public static final int BLEND_MODE_SCREEN = 14; + public static final int BLEND_MODE_OVERLAY = 15; + public static final int BLEND_MODE_DARKEN = 16; + public static final int BLEND_MODE_LIGHTEN = 17; + public static final int BLEND_MODE_COLOR_DODGE = 18; + public static final int BLEND_MODE_COLOR_BURN = 19; + public static final int BLEND_MODE_HARD_LIGHT = 20; + public static final int BLEND_MODE_SOFT_LIGHT = 21; + public static final int BLEND_MODE_DIFFERENCE = 22; + public static final int BLEND_MODE_EXCLUSION = 23; + public static final int BLEND_MODE_MULTIPLY = 24; + public static final int BLEND_MODE_HUE = 25; + public static final int BLEND_MODE_SATURATION = 26; + public static final int BLEND_MODE_COLOR = 27; + public static final int BLEND_MODE_LUMINOSITY = 28; + public static final int BLEND_MODE_NULL = 29; + public static final int PORTER_MODE_ADD = 30; + + public static final int FONT_NORMAL = 0; + public static final int FONT_BOLD = 1; + public static final int FONT_ITALIC = 2; + public static final int FONT_BOLD_ITALIC = 3; + + public static final int FONT_TYPE_DEFAULT = 0; + public static final int FONT_TYPE_SANS_SERIF = 1; + public static final int FONT_TYPE_SERIF = 2; + public static final int FONT_TYPE_MONOSPACE = 3; + + public static final int STYLE_FILL = 0; + public static final int STYLE_STROKE = 1; + public static final int STYLE_FILL_AND_STROKE = 2; + public static final int LINEAR_GRADIENT = 0; + public static final int RADIAL_GRADIENT = 1; + public static final int SWEEP_GRADIENT = 2; + + /** + * sets a shader that draws a linear gradient along a line. + * + * @param startX The x-coordinate for the start of the gradient line + * @param startY The y-coordinate for the start of the gradient line + * @param endX The x-coordinate for the end of the gradient line + * @param endY The y-coordinate for the end of the gradient line + * @param colors The sRGB colors to be distributed along the gradient line + * @param stops May be null. The relative positions [0..1] of + * each corresponding color in the colors array. If this is null, + * the colors are distributed evenly along the gradient line. + * @param tileMode The Shader tiling mode + */ + public void setLinearGradient(int[] colors, + float[] stops, + float startX, + float startY, + float endX, + float endY, + int tileMode) { + int startPos = mPos; + int len; + mArray[mPos++] = GRADIENT | (LINEAR_GRADIENT << 16); + mArray[mPos++] = len = (colors == null) ? 0 : colors.length; + for (int i = 0; i < len; i++) { + mArray[mPos++] = colors[i]; + } + + mArray[mPos++] = len = (stops == null) ? 0 : stops.length; + for (int i = 0; i < len; i++) { + mArray[mPos++] = Float.floatToRawIntBits(stops[i]); + } + mArray[mPos++] = Float.floatToRawIntBits(startX); + mArray[mPos++] = Float.floatToRawIntBits(startY); + mArray[mPos++] = Float.floatToRawIntBits(endX); + mArray[mPos++] = Float.floatToRawIntBits(endY); + mArray[mPos++] = tileMode; + } + + /** + * Set a shader that draws a sweep gradient around a center point. + * + * @param centerX The x-coordinate of the center + * @param centerY The y-coordinate of the center + * @param colors The sRGB colors to be distributed around the center. + * There must be at least 2 colors in the array. + * @param stops May be NULL. The relative position of + * each corresponding color in the colors array, beginning + * with 0 and ending with 1.0. If the values are not + * monotonic, the drawing may produce unexpected results. + * If positions is NULL, then the colors are automatically + * spaced evenly. + */ + public void setSweepGradient(int[] colors, float[] stops, float centerX, float centerY) { + int startPos = mPos; + int len; + mArray[mPos++] = GRADIENT | (SWEEP_GRADIENT << 16); + mArray[mPos++] = len = (colors == null) ? 0 : colors.length; + for (int i = 0; i < len; i++) { + mArray[mPos++] = colors[i]; + } + + mArray[mPos++] = len = (stops == null) ? 0 : stops.length; + for (int i = 0; i < len; i++) { + mArray[mPos++] = Float.floatToRawIntBits(stops[i]); + } + mArray[mPos++] = Float.floatToRawIntBits(centerX); + mArray[mPos++] = Float.floatToRawIntBits(centerY); + } + + /** + * Sets a shader that draws a radial gradient given the center and radius. + * + * @param centerX The x-coordinate of the center of the radius + * @param centerY The y-coordinate of the center of the radius + * @param radius Must be positive. The radius of the gradient. + * @param colors The sRGB colors distributed between the center and edge + * @param stops May be <code>null</code>. + * Valid values are between <code>0.0f</code> and + * <code>1.0f</code>. The relative position of each + * corresponding color in + * the colors array. If <code>null</code>, colors are + * distributed evenly + * between the center and edge of the circle. + * @param tileMode The Shader tiling mode + */ + public void setRadialGradient(int[] colors, + float[] stops, + float centerX, + float centerY, + float radius, + int tileMode) { + int startPos = mPos; + int len; + mArray[mPos++] = GRADIENT | (RADIAL_GRADIENT << 16); + mArray[mPos++] = len = (colors == null) ? 0 : colors.length; + for (int i = 0; i < len; i++) { + mArray[mPos++] = colors[i]; + } + mArray[mPos++] = len = (stops == null) ? 0 : stops.length; + + for (int i = 0; i < len; i++) { + mArray[mPos++] = Float.floatToRawIntBits(stops[i]); + } + mArray[mPos++] = Float.floatToRawIntBits(centerX); + mArray[mPos++] = Float.floatToRawIntBits(centerY); + mArray[mPos++] = Float.floatToRawIntBits(radius); + mArray[mPos++] = tileMode; + + } + + /** + * Create a color filter that uses the specified color and Porter-Duff mode. + * + * @param color The ARGB source color used with the Porter-Duff mode + * @param mode The porter-duff mode that is applied + */ + public void setColorFilter(int color, int mode) { + mArray[mPos] = COLOR_FILTER | (mode << 16); + mPos++; + mArray[mPos++] = color; + } + + /** + * Set the paint's text size. This value must be > 0 + * + * @param size set the paint's text size in pixel units. + */ + public void setTextSize(float size) { + int p = mPos; + mArray[mPos] = TEXT_SIZE; + mPos++; + mArray[mPos] = Float.floatToRawIntBits(size); + mPos++; + } + + /** + * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace + * @param weight 100-1000 + * @param italic tur + */ + public void setTextStyle(int fontType, int weight, boolean italic) { + int style = (weight & 0x3FF) | (italic ? 2048 : 0); // pack the weight and italic + mArray[mPos++] = TYPEFACE | (style << 16); + mArray[mPos++] = fontType; + } + + /** + * Set the width for stroking. + * Pass 0 to stroke in hairline mode. + * Hairlines always draws a single pixel independent of the canvas's matrix. + * + * @param width set the paint's stroke width, used whenever the paint's + * style is Stroke or StrokeAndFill. + */ + public void setStrokeWidth(float width) { + mArray[mPos] = STROKE_WIDTH; + mPos++; + mArray[mPos] = Float.floatToRawIntBits(width); + mPos++; + } + + public void setColor(int color) { + mArray[mPos] = COLOR; + mPos++; + mArray[mPos] = color; + mPos++; + } + + /** + * Set the paint's Cap. + * + * @param cap set the paint's line cap style, used whenever the paint's + * style is Stroke or StrokeAndFill. + */ + public void setStrokeCap(int cap) { + mArray[mPos] = STROKE_CAP | (cap << 16); + mPos++; + } + + public void setStyle(int style) { + mArray[mPos] = STYLE | (style << 16); + mPos++; + } + + public void setShader(int shader, String shaderString) { + mArray[mPos] = SHADER | (shader << 16); + mPos++; + } + + public void setAlpha(float alpha) { + mArray[mPos] = ALPHA; + mPos++; + mArray[mPos] = Float.floatToRawIntBits(alpha); + mPos++; + } + + /** + * Set the paint's stroke miter value. This is used to control the behavior + * of miter joins when the joins angle is sharp. This value must be >= 0. + * + * @param miter set the miter limit on the paint, used whenever the paint's + * style is Stroke or StrokeAndFill. + */ + public void setStrokeMiter(float miter) { + mArray[mPos] = STROKE_MITER; + mPos++; + mArray[mPos] = Float.floatToRawIntBits(miter); + mPos++; + } + + /** + * Set the paint's Join. + * + * @param join set the paint's Join, used whenever the paint's style is + * Stroke or StrokeAndFill. + */ + public void setStrokeJoin(int join) { + mArray[mPos] = STROKE_JOIN | (join << 16); + mPos++; + } + + public void setFilterBitmap(boolean filter) { + mArray[mPos] = FILTER_BITMAP | (filter ? (1 << 16) : 0); + mPos++; + } + + /** + * Set or clear the blend mode. A blend mode defines how source pixels + * (generated by a drawing command) are composited with the + * destination pixels + * (content of the render target). + * + * + * @param blendmode The blend mode to be installed in the paint + */ + public void setBlendMode(int blendmode) { + mArray[mPos] = BLEND_MODE | (blendmode << 16); + mPos++; + } + + /** + * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit + * AntiAliasing smooths out the edges of what is being drawn, but is has + * no impact on the interior of the shape. See setDither() and + * setFilterBitmap() to affect how colors are treated. + * + * @param aa true to set the antialias bit in the flags, false to clear it + */ + public void setAntiAlias(boolean aa) { + mArray[mPos] = ANTI_ALIAS | (((aa) ? 1 : 0) << 16); + mPos++; + } + + public void clear(long mask) { // unused for now + } + + public void reset() { + mPos = 0; + } + + public static String blendModeString(int mode) { + switch (mode) { + case PaintBundle.BLEND_MODE_CLEAR: + return "CLEAR"; + case PaintBundle.BLEND_MODE_SRC: + return "SRC"; + case PaintBundle.BLEND_MODE_DST: + return "DST"; + case PaintBundle.BLEND_MODE_SRC_OVER: + return "SRC_OVER"; + case PaintBundle.BLEND_MODE_DST_OVER: + return "DST_OVER"; + case PaintBundle.BLEND_MODE_SRC_IN: + return "SRC_IN"; + case PaintBundle.BLEND_MODE_DST_IN: + return "DST_IN"; + case PaintBundle.BLEND_MODE_SRC_OUT: + return "SRC_OUT"; + case PaintBundle.BLEND_MODE_DST_OUT: + return "DST_OUT"; + case PaintBundle.BLEND_MODE_SRC_ATOP: + return "SRC_ATOP"; + case PaintBundle.BLEND_MODE_DST_ATOP: + return "DST_ATOP"; + case PaintBundle.BLEND_MODE_XOR: + return "XOR"; + case PaintBundle.BLEND_MODE_PLUS: + return "PLUS"; + case PaintBundle.BLEND_MODE_MODULATE: + return "MODULATE"; + case PaintBundle.BLEND_MODE_SCREEN: + return "SCREEN"; + case PaintBundle.BLEND_MODE_OVERLAY: + return "OVERLAY"; + case PaintBundle.BLEND_MODE_DARKEN: + return "DARKEN"; + case PaintBundle.BLEND_MODE_LIGHTEN: + return "LIGHTEN"; + case PaintBundle.BLEND_MODE_COLOR_DODGE: + return "COLOR_DODGE"; + case PaintBundle.BLEND_MODE_COLOR_BURN: + return "COLOR_BURN"; + case PaintBundle.BLEND_MODE_HARD_LIGHT: + return "HARD_LIGHT"; + case PaintBundle.BLEND_MODE_SOFT_LIGHT: + return "SOFT_LIGHT"; + case PaintBundle.BLEND_MODE_DIFFERENCE: + return "DIFFERENCE"; + case PaintBundle.BLEND_MODE_EXCLUSION: + return "EXCLUSION"; + case PaintBundle.BLEND_MODE_MULTIPLY: + return "MULTIPLY"; + case PaintBundle.BLEND_MODE_HUE: + return "HUE"; + case PaintBundle.BLEND_MODE_SATURATION: + return "SATURATION"; + case PaintBundle.BLEND_MODE_COLOR: + return "COLOR"; + case PaintBundle.BLEND_MODE_LUMINOSITY: + return "LUMINOSITY"; + case PaintBundle.BLEND_MODE_NULL: + return "null"; + case PaintBundle.PORTER_MODE_ADD: + return "ADD"; + } + return "null"; + } + +} + diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java new file mode 100644 index 000000000000..994bf6d7e327 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java @@ -0,0 +1,130 @@ +/* + * 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.internal.widget.remotecompose.core.operations.paint; + +public class PaintChangeAdapter implements PaintChanges { + + @Override + public void setTextSize(float size) { + + } + + @Override + public void setTypeFace(int fontType, int weight, boolean italic) { + + } + + + @Override + public void setStrokeWidth(float width) { + + } + + @Override + public void setColor(int color) { + + } + + @Override + public void setStrokeCap(int cap) { + + } + + @Override + public void setStyle(int style) { + + } + + @Override + public void setShader(int shader, String shaderString) { + + } + + @Override + public void setImageFilterQuality(int quality) { + + } + + @Override + public void setAlpha(float a) { + + } + + @Override + public void setStrokeMiter(float miter) { + + } + + @Override + public void setStrokeJoin(int join) { + + } + + @Override + public void setFilterBitmap(boolean filter) { + + } + + @Override + public void setBlendMode(int blendmode) { + + } + + @Override + public void setAntiAlias(boolean aa) { + + } + + @Override + public void clear(long mask) { + + } + + @Override + public void setLinearGradient(int[] colorsArray, + float[] stopsArray, + float startX, + float startY, + float endX, + float endY, + int tileMode) { + + } + + @Override + public void setRadialGradient(int[] colorsArray, + float[] stopsArray, + float centerX, + float centerY, + float radius, + int tileMode) { + + } + + @Override + public void setSweepGradient(int[] colorsArray, + float[] stopsArray, + float centerX, + float centerY) { + + } + + @Override + public void setColorFilter(int color, int mode) { + + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java new file mode 100644 index 000000000000..87e58ac35930 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java @@ -0,0 +1,81 @@ +/* + * 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.internal.widget.remotecompose.core.operations.paint; + +public interface PaintChanges { + + + int CLEAR_TEXT_STYLE = 1 << (PaintBundle.TYPEFACE - 1); + int CLEAR_COLOR = 1 << (PaintBundle.COLOR - 1); + int CLEAR_STROKE_WIDTH = 1 << (PaintBundle.STROKE_WIDTH - 1); + int CLEAR_STROKE_MITER = 1 << (PaintBundle.STROKE_MITER - 1); + int CLEAR_CAP = 1 << (PaintBundle.STROKE_CAP - 1); + int CLEAR_STYLE = 1 << (PaintBundle.STYLE - 1); + int CLEAR_SHADER = 1 << (PaintBundle.SHADER - 1); + int CLEAR_IMAGE_FILTER_QUALITY = + 1 << (PaintBundle.IMAGE_FILTER_QUALITY - 1); + int CLEAR_RADIENT = 1 << (PaintBundle.GRADIENT - 1); + int CLEAR_ALPHA = 1 << (PaintBundle.ALPHA - 1); + int CLEAR_COLOR_FILTER = 1 << (PaintBundle.COLOR_FILTER - 1); + int VALID_BITS = 0x1FFF; // only the first 13 bit are valid now + + + void setTextSize(float size); + void setStrokeWidth(float width); + void setColor(int color); + void setStrokeCap(int cap); + void setStyle(int style); + void setShader(int shader, String shaderString); + void setImageFilterQuality(int quality); + void setAlpha(float a); + void setStrokeMiter(float miter); + void setStrokeJoin(int join); + void setFilterBitmap(boolean filter); + void setBlendMode(int mode); + void setAntiAlias(boolean aa); + void clear(long mask); + void setLinearGradient( + int[] colorsArray, + float[] stopsArray, + float startX, + float startY, + float endX, + float endY, + int tileMode + ); + + void setRadialGradient( + int[] colorsArray, + float[] stopsArray, + float centerX, + float centerY, + float radius, + int tileMode + ); + + void setSweepGradient( + int[] colorsArray, + float[] stopsArray, + float centerX, + float centerY + ); + + + void setColorFilter(int color, int mode); + + void setTypeFace(int fontType, int weight, boolean italic); +} + diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java new file mode 100644 index 000000000000..1c0bec76bb62 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java @@ -0,0 +1,64 @@ +/* + * 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.internal.widget.remotecompose.core.operations.paint; + +public interface TextPaint { + void setARGB(int a, int r, int g, int b); + + void setDither(boolean dither); + + void setElegantTextHeight(boolean elegant); + + void setEndHyphenEdit(int endHyphen); + + void setFakeBoldText(boolean fakeBoldText); + + void setFlags(int flags); + + void setFontFeatureSettings(String settings); + + void setHinting(int mode); + + void setLetterSpacing(float letterSpacing); + + void setLinearText(boolean linearText); + + void setShadowLayer(float radius, float dx, float dy, int shadowColor); + + void setStartHyphenEdit(int startHyphen); + + void setStrikeThruText(boolean strikeThruText); + + void setStrokeCap(int cap); + + void setSubpixelText(boolean subpixelText); + + void setTextAlign(int align); + + void setTextLocale(int locale); + + void setTextLocales(int localesArray); + + void setTextScaleX(float scaleX); + + void setTextSize(float textSize); + + void setTextSkewX(float skewX); + + void setUnderlineText(boolean underlineText); + + void setWordSpacing(float wordSpacing); +} diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java index 3799cf6baac9..d0d6e6982a16 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java @@ -16,12 +16,25 @@ package com.android.internal.widget.remotecompose.player.platform; import android.graphics.Bitmap; +import android.graphics.BlendMode; import android.graphics.Canvas; +import android.graphics.LinearGradient; import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RadialGradient; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.SweepGradient; +import android.graphics.Typeface; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.operations.ClipPath; +import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; +import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges; /** * An implementation of PaintContext for the Android Canvas. @@ -71,7 +84,8 @@ public class AndroidPaintContext extends PaintContext { int cdId) { AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; if (androidContext.mRemoteComposeState.containsId(imageId)) { - Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(imageId); + Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState + .getFromId(imageId); mCanvas.drawBitmap( bitmap, new Rect(srcLeft, srcTop, srcRight, srcBottom), @@ -89,5 +103,501 @@ public class AndroidPaintContext extends PaintContext { public void translate(float translateX, float translateY) { mCanvas.translate(translateX, translateY); } + + @Override + public void drawArc(float left, + float top, + float right, + float bottom, + float startAngle, + float sweepAngle) { + mCanvas.drawArc(left, top, right, bottom, startAngle, + sweepAngle, true, mPaint); + } + + @Override + public void drawBitmap(int id, + float left, + float top, + float right, + float bottom) { + AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; + if (androidContext.mRemoteComposeState.containsId(id)) { + Bitmap bitmap = + (Bitmap) androidContext.mRemoteComposeState.getFromId(id); + Rect src = new Rect(0, 0, + bitmap.getWidth(), bitmap.getHeight()); + RectF dst = new RectF(left, top, right, bottom); + mCanvas.drawBitmap(bitmap, src, dst, mPaint); + } + } + + @Override + public void drawCircle(float centerX, float centerY, float radius) { + mCanvas.drawCircle(centerX, centerY, radius, mPaint); + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2) { + mCanvas.drawLine(x1, y1, x2, y2, mPaint); + } + + @Override + public void drawOval(float left, float top, float right, float bottom) { + mCanvas.drawOval(left, top, right, bottom, mPaint); + } + + @Override + public void drawPath(int id, float start, float end) { + mCanvas.drawPath(getPath(id, start, end), mPaint); + } + + @Override + public void drawRect(float left, float top, float right, float bottom) { + mCanvas.drawRect(left, top, right, bottom, mPaint); + } + + @Override + public void drawRoundRect(float left, + float top, + float right, + float bottom, + float radiusX, + float radiusY) { + mCanvas.drawRoundRect(left, top, right, bottom, + radiusX, radiusY, mPaint); + } + + @Override + public void drawTextOnPath(int textId, + int pathId, + float hOffset, + float vOffset) { + mCanvas.drawTextOnPath(getText(textId), getPath(pathId, 0, 1), hOffset, vOffset, mPaint); + } + + @Override + public void drawTextRun(int textID, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + String textToPaint = getText(textID).substring(start, end); + mCanvas.drawText(textToPaint, x, y, mPaint); + } + + @Override + public void drawTweenPath(int path1Id, + int path2Id, + float tween, + float start, + float end) { + mCanvas.drawPath(getPath(path1Id, path2Id, tween, start, end), mPaint); + } + + private static PorterDuff.Mode origamiToPorterDuffMode(int mode) { + switch (mode) { + case PaintBundle.BLEND_MODE_CLEAR: + return PorterDuff.Mode.CLEAR; + case PaintBundle.BLEND_MODE_SRC: + return PorterDuff.Mode.SRC; + case PaintBundle.BLEND_MODE_DST: + return PorterDuff.Mode.DST; + case PaintBundle.BLEND_MODE_SRC_OVER: + return PorterDuff.Mode.SRC_OVER; + case PaintBundle.BLEND_MODE_DST_OVER: + return PorterDuff.Mode.DST_OVER; + case PaintBundle.BLEND_MODE_SRC_IN: + return PorterDuff.Mode.SRC_IN; + case PaintBundle.BLEND_MODE_DST_IN: + return PorterDuff.Mode.DST_IN; + case PaintBundle.BLEND_MODE_SRC_OUT: + return PorterDuff.Mode.SRC_OUT; + case PaintBundle.BLEND_MODE_DST_OUT: + return PorterDuff.Mode.DST_OUT; + case PaintBundle.BLEND_MODE_SRC_ATOP: + return PorterDuff.Mode.SRC_ATOP; + case PaintBundle.BLEND_MODE_DST_ATOP: + return PorterDuff.Mode.DST_ATOP; + case PaintBundle.BLEND_MODE_XOR: + return PorterDuff.Mode.XOR; + case PaintBundle.BLEND_MODE_SCREEN: + return PorterDuff.Mode.SCREEN; + case PaintBundle.BLEND_MODE_OVERLAY: + return PorterDuff.Mode.OVERLAY; + case PaintBundle.BLEND_MODE_DARKEN: + return PorterDuff.Mode.DARKEN; + case PaintBundle.BLEND_MODE_LIGHTEN: + return PorterDuff.Mode.LIGHTEN; + case PaintBundle.BLEND_MODE_MULTIPLY: + return PorterDuff.Mode.MULTIPLY; + case PaintBundle.PORTER_MODE_ADD: + return PorterDuff.Mode.ADD; + } + return PorterDuff.Mode.SRC_OVER; + } + + public static BlendMode origamiToBlendMode(int mode) { + switch (mode) { + case PaintBundle.BLEND_MODE_CLEAR: + return BlendMode.CLEAR; + case PaintBundle.BLEND_MODE_SRC: + return BlendMode.SRC; + case PaintBundle.BLEND_MODE_DST: + return BlendMode.DST; + case PaintBundle.BLEND_MODE_SRC_OVER: + return BlendMode.SRC_OVER; + case PaintBundle.BLEND_MODE_DST_OVER: + return BlendMode.DST_OVER; + case PaintBundle.BLEND_MODE_SRC_IN: + return BlendMode.SRC_IN; + case PaintBundle.BLEND_MODE_DST_IN: + return BlendMode.DST_IN; + case PaintBundle.BLEND_MODE_SRC_OUT: + return BlendMode.SRC_OUT; + case PaintBundle.BLEND_MODE_DST_OUT: + return BlendMode.DST_OUT; + case PaintBundle.BLEND_MODE_SRC_ATOP: + return BlendMode.SRC_ATOP; + case PaintBundle.BLEND_MODE_DST_ATOP: + return BlendMode.DST_ATOP; + case PaintBundle.BLEND_MODE_XOR: + return BlendMode.XOR; + case PaintBundle.BLEND_MODE_PLUS: + return BlendMode.PLUS; + case PaintBundle.BLEND_MODE_MODULATE: + return BlendMode.MODULATE; + case PaintBundle.BLEND_MODE_SCREEN: + return BlendMode.SCREEN; + case PaintBundle.BLEND_MODE_OVERLAY: + return BlendMode.OVERLAY; + case PaintBundle.BLEND_MODE_DARKEN: + return BlendMode.DARKEN; + case PaintBundle.BLEND_MODE_LIGHTEN: + return BlendMode.LIGHTEN; + case PaintBundle.BLEND_MODE_COLOR_DODGE: + return BlendMode.COLOR_DODGE; + case PaintBundle.BLEND_MODE_COLOR_BURN: + return BlendMode.COLOR_BURN; + case PaintBundle.BLEND_MODE_HARD_LIGHT: + return BlendMode.HARD_LIGHT; + case PaintBundle.BLEND_MODE_SOFT_LIGHT: + return BlendMode.SOFT_LIGHT; + case PaintBundle.BLEND_MODE_DIFFERENCE: + return BlendMode.DIFFERENCE; + case PaintBundle.BLEND_MODE_EXCLUSION: + return BlendMode.EXCLUSION; + case PaintBundle.BLEND_MODE_MULTIPLY: + return BlendMode.MULTIPLY; + case PaintBundle.BLEND_MODE_HUE: + return BlendMode.HUE; + case PaintBundle.BLEND_MODE_SATURATION: + return BlendMode.SATURATION; + case PaintBundle.BLEND_MODE_COLOR: + return BlendMode.COLOR; + case PaintBundle.BLEND_MODE_LUMINOSITY: + return BlendMode.LUMINOSITY; + case PaintBundle.BLEND_MODE_NULL: + return null; + } + return null; + } + + @Override + public void applyPaint(PaintBundle mPaintData) { + mPaintData.applyPaintChange(new PaintChanges() { + @Override + public void setTextSize(float size) { + mPaint.setTextSize(size); + } + + @Override + public void setTypeFace(int fontType, int weight, boolean italic) { + int[] type = new int[]{Typeface.NORMAL, Typeface.BOLD, + Typeface.ITALIC, Typeface.BOLD_ITALIC}; + + switch (fontType) { + case PaintBundle.FONT_TYPE_DEFAULT: { + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.DEFAULT); + } else { + mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, + weight, italic)); + } + break; + } + case PaintBundle.FONT_TYPE_SERIF: { + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.SERIF); + } else { + mPaint.setTypeface(Typeface.create(Typeface.SERIF, + weight, italic)); + } + break; + } + case PaintBundle.FONT_TYPE_SANS_SERIF: { + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.SANS_SERIF); + } else { + mPaint.setTypeface( + Typeface.create(Typeface.SANS_SERIF, + weight, italic)); + } + break; + } + case PaintBundle.FONT_TYPE_MONOSPACE: { + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.MONOSPACE); + } else { + mPaint.setTypeface( + Typeface.create(Typeface.MONOSPACE, + weight, italic)); + } + + break; + } + } + + + } + + + @Override + public void setStrokeWidth(float width) { + mPaint.setStrokeWidth(width); + } + + @Override + public void setColor(int color) { + mPaint.setColor(color); + } + + @Override + public void setStrokeCap(int cap) { + mPaint.setStrokeCap(Paint.Cap.values()[cap]); + } + + @Override + public void setStyle(int style) { + mPaint.setStyle(Paint.Style.values()[style]); + } + + @Override + public void setShader(int shader, String shaderString) { + + } + + @Override + public void setImageFilterQuality(int quality) { + System.out.println(">>>>>>>>>>>> "); + } + + @Override + public void setBlendMode(int mode) { + mPaint.setBlendMode(origamiToBlendMode(mode)); + } + + @Override + public void setAlpha(float a) { + mPaint.setAlpha((int) (255 * a)); + } + + @Override + public void setStrokeMiter(float miter) { + mPaint.setStrokeMiter(miter); + } + + @Override + public void setStrokeJoin(int join) { + mPaint.setStrokeJoin(Paint.Join.values()[join]); + } + + @Override + public void setFilterBitmap(boolean filter) { + mPaint.setFilterBitmap(filter); + } + + + @Override + public void setAntiAlias(boolean aa) { + mPaint.setAntiAlias(aa); + } + + @Override + public void clear(long mask) { + if (true) return; + long m = mask; + int k = 1; + while (m > 0) { + if ((m & 1) == 1L) { + switch (k) { + + case PaintBundle.COLOR_FILTER: + mPaint.setColorFilter(null); + System.out.println(">>>>>>>>>>>>> CLEAR!!!!"); + break; + } + } + k++; + m = m >> 1; + } + } + + Shader.TileMode[] mTilesModes = new Shader.TileMode[]{ + Shader.TileMode.CLAMP, + Shader.TileMode.REPEAT, + Shader.TileMode.MIRROR}; + + + @Override + public void setLinearGradient(int[] colors, + float[] stops, + float startX, + float startY, + float endX, + float endY, + int tileMode) { + mPaint.setShader(new LinearGradient(startX, + startY, + endX, + endY, colors, stops, mTilesModes[tileMode])); + + } + + @Override + public void setRadialGradient(int[] colors, + float[] stops, + float centerX, + float centerY, + float radius, + int tileMode) { + mPaint.setShader(new RadialGradient(centerX, centerY, radius, + colors, stops, mTilesModes[tileMode])); + } + + @Override + public void setSweepGradient(int[] colors, + float[] stops, + float centerX, + float centerY) { + mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops)); + + } + + @Override + public void setColorFilter(int color, int mode) { + PorterDuff.Mode pmode = origamiToPorterDuffMode(mode); + System.out.println("setting color filter to " + pmode.name()); + if (pmode != null) { + mPaint.setColorFilter( + new PorterDuffColorFilter(color, pmode)); + } + } + }); + } + + @Override + public void mtrixScale(float scaleX, + float scaleY, + float centerX, + float centerY) { + if (Float.isNaN(centerX)) { + mCanvas.scale(scaleX, scaleY); + } else { + mCanvas.scale(scaleX, scaleY, centerX, centerY); + } + } + + @Override + public void matrixTranslate(float translateX, float translateY) { + mCanvas.translate(translateX, translateY); + } + + @Override + public void matrixSkew(float skewX, float skewY) { + mCanvas.skew(skewX, skewY); + } + + @Override + public void matrixRotate(float rotate, float pivotX, float pivotY) { + if (Float.isNaN(pivotX)) { + mCanvas.rotate(rotate); + } else { + mCanvas.rotate(rotate, pivotX, pivotY); + + } + } + + @Override + public void matrixSave() { + mCanvas.save(); + } + + @Override + public void matrixRestore() { + mCanvas.restore(); + } + + @Override + public void clipRect(float left, float top, float right, float bottom) { + mCanvas.clipRect(left, top, right, bottom); + } + + @Override + public void clipPath(int pathId, int regionOp) { + Path path = getPath(pathId, 0, 1); + if (regionOp == ClipPath.DIFFERENCE) { + mCanvas.clipOutPath(path); // DIFFERENCE + } else { + mCanvas.clipPath(path); // INTERSECT + } + } + + private Path getPath(int path1Id, + int path2Id, + float tween, + float start, + float end) { + if (tween == 0.0f) { + return getPath(path1Id, start, end); + } + if (tween == 1.0f) { + return getPath(path2Id, start, end); + } + AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; + float[] data1 = + (float[]) androidContext.mRemoteComposeState.getFromId(path1Id); + float[] data2 = + (float[]) androidContext.mRemoteComposeState.getFromId(path2Id); + float[] tmp = new float[data2.length]; + for (int i = 0; i < tmp.length; i++) { + if (Float.isNaN(data1[i]) || Float.isNaN(data2[i])) { + tmp[i] = data1[i]; + } else { + tmp[i] = (data2[i] - data1[i]) * tween + data1[i]; + } + } + Path path = new Path(); + FloatsToPath.genPath(path, tmp, start, end); + return path; + } + + private Path getPath(int id, float start, float end) { + AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext; + Path path = new Path(); + if (androidContext.mRemoteComposeState.containsId(id)) { + float[] data = + (float[]) androidContext.mRemoteComposeState.getFromId(id); + FloatsToPath.genPath(path, data, start, end); + } + return path; + } + + private String getText(int id) { + return (String) mContext.mRemoteComposeState.getFromId(id); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index ce15855fecfc..270e96f11942 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -43,6 +43,13 @@ class AndroidRemoteContext extends RemoteContext { // Data handling /////////////////////////////////////////////////////////////////////////////////////////////// + @Override + public void loadPathData(int instanceId, float[] floatPath) { + if (!mRemoteComposeState.containsId(instanceId)) { + mRemoteComposeState.cache(instanceId, floatPath); + } + } + /** * Decode a byte array into an image and cache it using the given imageId * diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java new file mode 100644 index 000000000000..2d766f8da295 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java @@ -0,0 +1,115 @@ +/* + * 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.internal.widget.remotecompose.player.platform; + +import static com.android.internal.widget.remotecompose.core.operations.Utils.idFromNan; + +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.os.Build; + +import com.android.internal.widget.remotecompose.core.operations.PathData; + +public class FloatsToPath { + public static void genPath(Path retPath, + float[] floatPath, + float start, + float stop) { + int i = 0; + Path path = new Path(); // todo this should be cached for performance + while (i < floatPath.length) { + switch (idFromNan(floatPath[i])) { + case PathData.MOVE: { + i++; + path.moveTo(floatPath[i + 0], floatPath[i + 1]); + i += 2; + } + break; + case PathData.LINE: { + i += 3; + path.lineTo(floatPath[i + 0], floatPath[i + 1]); + i += 2; + } + break; + case PathData.QUADRATIC: { + i += 3; + path.quadTo( + floatPath[i + 0], + floatPath[i + 1], + floatPath[i + 2], + floatPath[i + 3] + ); + i += 4; + + } + break; + case PathData.CONIC: { + i += 3; + if (Build.VERSION.SDK_INT >= 34) { + path.conicTo( + floatPath[i + 0], floatPath[i + 1], + floatPath[i + 2], floatPath[i + 3], + floatPath[i + 4] + ); + } + i += 5; + } + break; + case PathData.CUBIC: { + i += 3; + path.cubicTo( + floatPath[i + 0], floatPath[i + 1], + floatPath[i + 2], floatPath[i + 3], + floatPath[i + 4], floatPath[i + 5] + ); + i += 6; + } + break; + case PathData.CLOSE: { + + path.close(); + i++; + } + break; + case PathData.DONE: { + i++; + } + break; + default: { + System.err.println(" Odd command " + + idFromNan(floatPath[i])); + } + } + } + + retPath.reset(); + if (start > 0f || stop < 1f) { + if (start < stop) { + + PathMeasure measure = new PathMeasure(); // todo cached + measure.setPath(path, false); + float len = measure.getLength(); + float scaleStart = Math.max(start, 0f) * len; + float scaleStop = Math.min(stop, 1f) * len; + measure.getSegment(scaleStart, scaleStop, retPath, + true); + } + } else { + + retPath.addPath(path); + } + } +} diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 62d58b65e62a..c05ea3d65562 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -254,6 +254,17 @@ </intent-filter> </activity> + <activity android:name="android.widget.AbsListViewActivity" + android:label="AbsListViewActivity" + android:screenOrientation="portrait" + android:exported="true" + android:theme="@android:style/Theme.Material.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + <activity android:name="android.widget.DatePickerActivity" android:label="DatePickerActivity" android:screenOrientation="portrait" diff --git a/core/tests/coretests/res/layout/activity_abslist_view.xml b/core/tests/coretests/res/layout/activity_abslist_view.xml new file mode 100644 index 000000000000..85b4f11fb9ad --- /dev/null +++ b/core/tests/coretests/res/layout/activity_abslist_view.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <view + class="android.widget.AbsListViewFunctionalTest$MyListView" + android:id="@+id/list_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> +</LinearLayout>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index 0c1e8793bfc9..38288dcb3b9d 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -421,6 +421,47 @@ public class ResourcesManagerTest extends TestCase { ResourcesManager.setInstance(oriResourcesManager); } + @Test + @SmallTest + @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS) + public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration() + throws PackageManager.NameNotFoundException { + ResourcesManager oriResourcesManager = ResourcesManager.getInstance(); + ResourcesManager.setInstance(mResourcesManager); + + Resources old_resources = mResourcesManager.getResources( + null, APP_ONE_RES_DIR, null, null, null, null, null, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + assertNotNull(old_resources); + ResourcesImpl oldImpl = old_resources.getImpl(); + + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0); + Resources.registerResourcePaths(TEST_LIB, appInfo); + + // Create another resources with identical parameters. + Resources resources = mResourcesManager.getResources( + null, APP_ONE_RES_DIR, null, null, null, null, null, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + assertNotNull(resources); + // For a normal ResourcesImpl redirect, new Resources may find an old ResourcesImpl cache + // and reuse it based on the ResourcesKey. But for shared library ResourcesImpl redirect, + // new created Resources should never reuse any old impl, it has to recreate a new impl + // which has proper asset paths appended. + assertNotSame(oldImpl, resources.getImpl()); + + String[] resourcePaths = appInfo.getAllApkPaths(); + resourcePaths = removeDuplicates(resourcePaths); + ApkAssets[] loadedAssets = resources.getAssets().getApkAssets(); + assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); + + // Package resources' paths should be cached in ResourcesManager. + assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() + .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); + + // Revert the ResourcesManager instance back. + ResourcesManager.setInstance(oriResourcesManager); + } + private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) { for (int i = 0; i < resourcePaths.length; i++) { if (!resourcePaths[i].endsWith(".apk")) { diff --git a/core/tests/coretests/src/android/widget/AbsListViewActivity.java b/core/tests/coretests/src/android/widget/AbsListViewActivity.java new file mode 100644 index 000000000000..a617fa433a53 --- /dev/null +++ b/core/tests/coretests/src/android/widget/AbsListViewActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; + +/** + * An activity for testing the AbsListView widget. + */ +public class AbsListViewActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_abslist_view); + } +} diff --git a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java new file mode 100644 index 000000000000..ceea6caf410b --- /dev/null +++ b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.AttributeSet; +import android.util.PollingCheck; + +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.compatibility.common.util.WidgetTestUtils; +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@MediumTest +public class AbsListViewFunctionalTest { + private final String[] mCountryList = new String[] { + "Argentina", "Australia", "Belize", "Botswana", "Brazil", "Cameroon", "China", "Cyprus", + "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany", + "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy", + "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar", + "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia", + "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu", + "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe" + }; + private AbsListViewActivity mActivity; + private MyListView mMyListView; + + @Rule + public ActivityTestRule<AbsListViewActivity> mActivityRule = new ActivityTestRule<>( + AbsListViewActivity.class); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + public void setUp() throws Exception { + mActivity = mActivityRule.getActivity(); + mMyListView = (MyListView) mActivity.findViewById(R.id.list_view); + } + + @Test + @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API) + public void testLsitViewSetVelocity() throws Throwable { + final ArrayList<String> items = new ArrayList<>(Arrays.asList(mCountryList)); + final ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, + android.R.layout.simple_list_item_1, items); + + WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMyListView, + () -> mMyListView.setAdapter(adapter)); + mActivityRule.runOnUiThread(() -> { + // Create an adapter to display the list + mMyListView.setFrameContentVelocity(0); + }); + // set setFrameContentVelocity shouldn't do anything. + assertEquals(mMyListView.isSetVelocityCalled, false); + + mActivityRule.runOnUiThread(() -> { + mMyListView.fling(100); + }); + PollingCheck.waitFor(100, () -> mMyListView.isSetVelocityCalled); + // set setFrameContentVelocity should be called when fling. + assertTrue(mMyListView.isSetVelocityCalled); + } + + public static class MyListView extends ListView { + + public boolean isSetVelocityCalled; + + public MyListView(Context context) { + super(context); + } + + public MyListView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public MyListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void setFrameContentVelocity(float pixelsPerSecond) { + if (pixelsPerSecond != 0) { + isSetVelocityCalled = true; + } + } + } +} diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java index 08977265667c..cf7c5491f787 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java @@ -38,6 +38,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -73,8 +74,8 @@ public final class DeviceStateInfoTest { @Test public void create() { - final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1, - DEVICE_STATE_2); + final ArrayList<DeviceState> supportedStates = new ArrayList<>( + List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2)); final DeviceState baseState = DEVICE_STATE_0; final DeviceState currentState = DEVICE_STATE_2; @@ -87,8 +88,8 @@ public final class DeviceStateInfoTest { @Test public void equals() { - final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1, - DEVICE_STATE_2); + final ArrayList<DeviceState> supportedStates = new ArrayList<>( + List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2)); final DeviceState baseState = DEVICE_STATE_0; final DeviceState currentState = DEVICE_STATE_2; @@ -100,15 +101,14 @@ public final class DeviceStateInfoTest { Assert.assertEquals(info, sameInfo); final DeviceStateInfo differentInfo = new DeviceStateInfo( - List.of(DEVICE_STATE_0, DEVICE_STATE_2), baseState, - currentState); + new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_2)), baseState, currentState); assertNotEquals(info, differentInfo); } @Test public void diff_sameObject() { - final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1, - DEVICE_STATE_2); + final ArrayList<DeviceState> supportedStates = new ArrayList<>( + List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2)); final DeviceState baseState = DEVICE_STATE_0; final DeviceState currentState = DEVICE_STATE_2; @@ -118,10 +118,10 @@ public final class DeviceStateInfoTest { @Test public void diff_differentSupportedStates() { - final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0, - DEVICE_STATE_0); - final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_2), + final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_0); + final DeviceStateInfo otherInfo = new DeviceStateInfo( + new ArrayList<>(List.of(DEVICE_STATE_2)), DEVICE_STATE_0, DEVICE_STATE_0); final int diff = info.diff(otherInfo); assertTrue((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0); assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0); @@ -130,10 +130,10 @@ public final class DeviceStateInfoTest { @Test public void diff_differentNonOverrideState() { - final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_1, - DEVICE_STATE_0); - final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1), - DEVICE_STATE_2, DEVICE_STATE_0); + final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)), + DEVICE_STATE_1, DEVICE_STATE_0); + final DeviceStateInfo otherInfo = new DeviceStateInfo( + new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_2, DEVICE_STATE_0); final int diff = info.diff(otherInfo); assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0); assertTrue((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0); @@ -142,10 +142,10 @@ public final class DeviceStateInfoTest { @Test public void diff_differentState() { - final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0, - DEVICE_STATE_1); - final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1), - DEVICE_STATE_0, DEVICE_STATE_2); + final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)), + DEVICE_STATE_0, DEVICE_STATE_1); + final DeviceStateInfo otherInfo = new DeviceStateInfo( + new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_2); final int diff = info.diff(otherInfo); assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0); assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0); @@ -154,8 +154,8 @@ public final class DeviceStateInfoTest { @Test public void writeToParcel() { - final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1, - DEVICE_STATE_2); + final ArrayList<DeviceState> supportedStates = new ArrayList<>( + List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2)); final DeviceState nonOverrideState = DEVICE_STATE_0; final DeviceState state = DEVICE_STATE_2; final DeviceStateInfo originalInfo = diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index ee238c0a5533..f4d363167a75 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -40,6 +40,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -276,7 +277,7 @@ public final class DeviceStateManagerGlobalTest { new DeviceState.Configuration.Builder(mergedBaseState, "" /* name */).build()); final DeviceState state = new DeviceState( new DeviceState.Configuration.Builder(mergedState, "" /* name */).build()); - return new DeviceStateInfo(mSupportedDeviceStates, baseState, state); + return new DeviceStateInfo(new ArrayList<>(mSupportedDeviceStates), baseState, state); } private void notifyDeviceStateInfoChanged() { diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index e441c531a4c9..199e9293ad65 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -120,24 +120,6 @@ public final class FontFamily { } /** - * Returns true if the passed font files can be used for building a variable font family - * that automatically adjust the `wght` and `ital` axes value for the requested - * weight/italic style values. - * - * This method can be used for checking that the provided font files can be used for - * building a variable font family created with {@link #buildVariableFamily()}. - * If this function returns false, the {@link #buildVariableFamily()} will fail and - * return null. - * - * @return true if a variable font can be built from the given fonts. Otherwise, false. - */ - @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) - public boolean canBuildVariableFamily() { - int variableFamilyType = analyzeAndResolveVariableType(mFonts); - return variableFamilyType != VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; - } - - /** * Build a variable font family that automatically adjust the `wght` and `ital` axes value * for the requested weight/italic style values. * @@ -158,9 +140,11 @@ public final class FontFamily { * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight * value of the font is ignored. * - * If none of the above conditions are met, this function return {@code null}. Please check - * that your font files meet the above requirements or consider using the {@link #build()} - * method. + * If none of the above conditions are met, the provided font files cannot be used for + * variable font family and this function returns {@code null}. Even if this function + * returns {@code null}, you can still use {@link #build()} method for creating FontFamily + * instance with manually specifying variation settings by using + * {@link Font.Builder#setFontVariationSettings(String)}. * * @return A variable font family. null if a variable font cannot be built from the given * fonts. diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 9b14ce467662..b749a06bd516 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -165,15 +165,27 @@ java_library { }, } +filegroup { + name: "wm_shell-shared-aidls", + + srcs: [ + "shared/**/*.aidl", + ], + + path: "shared/src", +} + java_library { name: "WindowManager-Shell-shared", srcs: [ "shared/**/*.java", "shared/**/*.kt", + ":wm_shell-shared-aidls", ], static_libs: [ "androidx.dynamicanimation_dynamicanimation", + "jsr330", ], } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl index 72fba3bb7de4..8481c446c6aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.transition; +package com.android.wm.shell.shared; import android.window.RemoteTransition; import android.window.TransitionFilter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl index 7f4a8f1d476a..526407e25d98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.wm.shell.transition; +package com.android.wm.shell.shared; import android.view.SurfaceControl; import android.window.RemoteTransition; import android.window.TransitionFilter; -import com.android.wm.shell.transition.IHomeTransitionListener; +import com.android.wm.shell.shared.IHomeTransitionListener; /** * Interface that is exposed to remote callers to manipulate the transitions feature. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java index da39017a0313..5e49f559ca64 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.wm.shell.transition; +package com.android.wm.shell.shared; import android.annotation.NonNull; import android.window.RemoteTransition; import android.window.TransitionFilter; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; /** * Interface to manage remote transitions. diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java new file mode 100644 index 000000000000..a1496ac1d33b --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java @@ -0,0 +1,34 @@ +/* + * 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.wm.shell.shared.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync + * instead of the app vsync. + */ +@Documented +@Inherited +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ChoreographerSfVsync {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java index 9ac7a12bc509..52a717b3a60c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.common.annotations; +package com.android.wm.shell.shared.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java new file mode 100644 index 000000000000..ae5188cf8093 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java @@ -0,0 +1,31 @@ +/* + * 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.wm.shell.shared.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** Annotates a method or class that is called from an external thread to the Shell threads. */ +@Documented +@Inherited +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ExternalThread {} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java new file mode 100644 index 000000000000..bd2887e39ef1 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java @@ -0,0 +1,31 @@ +/* + * 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.wm.shell.shared.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */ +@Documented +@Inherited +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ShellAnimationThread {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java index 4cd3c903f2f8..586ac8297e26 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.common.annotations; +package com.android.wm.shell.shared.annotations; import java.lang.annotation.Documented; diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java new file mode 100644 index 000000000000..6c879a491fe0 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java @@ -0,0 +1,31 @@ +/* + * 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.wm.shell.shared.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** Annotates a method or qualifies a provider that runs on the Shell main-thread */ +@Documented +@Inherited +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ShellMainThread {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java index c2fd54fd96d7..4887dbe81b25 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.common.annotations; +package com.android.wm.shell.shared.annotations; import java.lang.annotation.Documented; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 8d8dc10951a6..26432111efdc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -20,7 +20,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.window.BackEvent; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; /** * Interface for external process to get access to the Back animation related methods. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 9b9798c6d93b..ad3be3d0b022 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -69,8 +69,8 @@ import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 7561a266c5ec..3253cac23ce8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -42,8 +42,8 @@ import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.Interpolators -import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.abs import kotlin.math.max diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index cfd9fb613414..cae2e80d5320 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -49,7 +49,7 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import javax.inject.Inject; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index fcf500a60166..e33aa7568d09 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -54,7 +54,7 @@ import com.android.internal.dynamicanimation.animation.SpringForce; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import javax.inject.Inject; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 4455a3caa7d9..ce8a460f10d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -101,14 +101,14 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.bubbles.BubbleBarLocation; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.PinnedStackListenerForwarder; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 26077cf7057b..127a49fc7875 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -37,8 +37,8 @@ import android.window.ScreenCapture.SynchronousScreenCaptureListener; import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index b828aac39040..2873d58439cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -28,7 +28,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; import java.util.concurrent.CopyOnWriteArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index 8353900be0ef..dcbc72ab0d32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -34,7 +34,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index ca06024a9adb..55dc793cc3b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -30,7 +30,7 @@ import android.view.inputmethod.ImeTracker; import androidx.annotation.BinderThread; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; import java.util.concurrent.CopyOnWriteArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index 53683c67d825..43c92cab6a68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java @@ -33,7 +33,7 @@ import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; import java.lang.annotation.Retention; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java deleted file mode 100644 index 4009ad21b9b8..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.android.wm.shell.common.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -/** - * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync - * instead of the app vsync. - */ -@Documented -@Inherited -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface ChoreographerSfVsync {}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java deleted file mode 100644 index 7560f71d1f98..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.wm.shell.common.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -/** Annotates a method or class that is called from an external thread to the Shell threads. */ -@Documented -@Inherited -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface ExternalThread {}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java deleted file mode 100644 index 0479f8780c79..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.wm.shell.common.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */ -@Documented -@Inherited -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface ShellAnimationThread {}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java deleted file mode 100644 index 423f4ce3bfd4..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.wm.shell.common.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -/** Annotates a method or qualifies a provider that runs on the Shell main-thread */ -@Documented -@Inherited -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface ShellMainThread {}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java index 317e48e19c13..c421dec025f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java @@ -28,7 +28,7 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.Map; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java index fa2e23647a39..cf3ad4299cea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java @@ -24,8 +24,8 @@ import android.provider.DeviceConfig; import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.shared.annotations.ShellMainThread; import javax.inject.Inject; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java index 216da070754b..011093718671 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java @@ -31,10 +31,9 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.dagger.pip.TvPipModule; -import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.tv.TvSplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 512211460753..73228de83c0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -58,10 +58,6 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ShellAnimationThread; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; @@ -92,6 +88,11 @@ import com.android.wm.shell.performance.PerfHintController; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.shared.ShellTransitions; +import com.android.wm.shell.shared.annotations.ShellAnimationThread; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; @@ -106,7 +107,6 @@ import com.android.wm.shell.taskview.TaskViewFactory; import com.android.wm.shell.taskview.TaskViewFactoryController; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.HomeTransitionObserver; -import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldAnimationController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index 0cc545a7724a..c5644a8f6876 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -33,11 +33,11 @@ import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ExternalMainThread; -import com.android.wm.shell.common.annotations.ShellAnimationThread; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.shared.annotations.ExternalMainThread; +import com.android.wm.shell.shared.annotations.ShellAnimationThread; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; import dagger.Module; import dagger.Provides; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 04f0f44d2876..b933e5d70586 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -52,9 +52,6 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ShellAnimationThread; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; @@ -77,6 +74,9 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.shared.annotations.ShellAnimationThread; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 1e3d7fb06da2..d644006cde81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -29,7 +29,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; @@ -56,6 +55,7 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 458ea05e620d..ae07812a2427 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -25,7 +25,6 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; @@ -38,6 +37,7 @@ import com.android.wm.shell.pip2.phone.PhonePipMenuController; import com.android.wm.shell.pip2.phone.PipController; import com.android.wm.shell.pip2.phone.PipScheduler; import com.android.wm.shell.pip2.phone.PipTransition; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index 54c2aeab4976..8d1b15c1e631 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -29,7 +29,6 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.LegacySizeSpecSource; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipDisplayLayoutState; @@ -53,6 +52,7 @@ import com.android.wm.shell.pip.tv.TvPipMenuController; import com.android.wm.shell.pip.tv.TvPipNotificationController; import com.android.wm.shell.pip.tv.TvPipTaskOrganizer; import com.android.wm.shell.pip.tv.TvPipTransition; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java index 5889da12d6e9..df1b06225fda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java @@ -18,7 +18,7 @@ package com.android.wm.shell.desktopmode; import android.graphics.Region; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.concurrent.Executor; import java.util.function.Consumer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 1b1c96764e88..c369061810d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -61,8 +61,6 @@ import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue -import com.android.wm.shell.common.annotations.ExternalThread -import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.compatui.isSingleTopActivityTranslucent @@ -72,6 +70,8 @@ import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.annotations.ExternalThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE import com.android.wm.shell.sysui.ShellCommandHandler diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 7da1b23dd5b1..165feec58455 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -67,8 +67,8 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ExternalMainThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ExternalMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 73de231fb63a..863a51ad575b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -48,8 +48,8 @@ import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java index 33c299f0b161..4215b2cc5f29 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java @@ -19,7 +19,7 @@ package com.android.wm.shell.keyguard; import android.annotation.NonNull; import android.window.IRemoteTransition; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; /** * Interface exposed to SystemUI Keyguard to register handlers for running diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index 2ee334873780..b000e3228b9a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -18,7 +18,7 @@ package com.android.wm.shell.onehanded; import android.os.SystemProperties; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; /** * Interface to engage one handed feature. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 679d4ca2ac48..39b9000856f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -55,7 +55,7 @@ import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index a9aa6badcfe2..7b1ef5c6cddd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -18,7 +18,7 @@ package com.android.wm.shell.pip; import android.graphics.Rect; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.function.Consumer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index bd186ba22588..cdeb00b7b9e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -82,7 +82,6 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; @@ -92,6 +91,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index 2616b8b08bf1..eebd13370321 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -16,7 +16,7 @@ package com.android.wm.shell.recents; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 370720746808..f9fcfacf47f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -49,11 +49,11 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 2b433e9c4227..576219769e61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -21,8 +21,8 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.graphics.Rect; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.concurrent.Executor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 3e34c303e161..c3261bbc7351 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -90,7 +90,6 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitScreenUtils; @@ -99,6 +98,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 4465aef0258d..3353c7bd81c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -47,8 +47,8 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; /** * A class which able to draw splash screen or snapshot as the starting window for a task. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index 2f6edc226c45..5ced1fb41a41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -45,7 +45,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.io.PrintWriter; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java index a7e4b0119480..f0a2315d7deb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java @@ -19,7 +19,7 @@ package com.android.wm.shell.taskview; import android.annotation.UiContext; import android.content.Context; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.concurrent.Executor; import java.util.function.Consumer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java index 7eed5883043d..e4fcff0c372a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java @@ -22,7 +22,7 @@ import android.content.Context; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.concurrent.Executor; import java.util.function.Consumer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index cb2944c120e0..c9185ae39114 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -32,6 +32,7 @@ import android.window.TransitionInfo; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; +import com.android.wm.shell.shared.IHomeTransitionListener; import com.android.wm.shell.shared.TransitionUtil; /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index a77602b3d2d0..437a00e4a160 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -76,10 +76,13 @@ import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.IHomeTransitionListener; +import com.android.wm.shell.shared.IShellTransitions; +import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.TransitionUtil; +import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 66efa02de764..e7d37addb368 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -51,6 +51,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.shared.IHomeTransitionListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 1905fa8ce612..82d43bcaaec9 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -65,6 +65,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; /** MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components. @@ -2014,6 +2015,23 @@ final public class MediaCodec { } } + // HACKY(b/325389296): aconfig flag accessors may not work in all contexts where MediaCodec API + // is used, so allow accessors to fail. In those contexts use a default value, normally false. + + /* package private */ + static boolean GetFlag(Supplier<Boolean> flagValueSupplier) { + return GetFlag(flagValueSupplier, false /* defaultValue */); + } + + /* package private */ + static boolean GetFlag(Supplier<Boolean> flagValueSupplier, boolean defaultValue) { + try { + return flagValueSupplier.get(); + } catch (java.lang.RuntimeException e) { + return defaultValue; + } + } + private boolean mHasSurface = false; /** @@ -2346,7 +2364,7 @@ final public class MediaCodec { } // at the moment no codecs support detachable surface - if (android.media.codec.Flags.nullOutputSurface()) { + if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) { // Detached surface flag is only meaningful if surface is null. Otherwise, it is // ignored. if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) { diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index abad46046890..8ff4305a9817 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -23,6 +23,7 @@ import static android.media.codec.Flags.FLAG_HLG_EDITING; import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; +import static android.media.MediaCodec.GetFlag; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -827,10 +828,10 @@ public final class MediaCodecInfo { features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false)); features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false)); features.add(new Feature(FEATURE_LowLatency, (1 << 7), true)); - if (android.media.codec.Flags.dynamicColorAspects()) { + if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) { features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true)); } - if (android.media.codec.Flags.nullOutputSurface()) { + if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) { features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true)); } @@ -851,10 +852,10 @@ public final class MediaCodecInfo { features.add(new Feature(FEATURE_QpBounds, (1 << 3), false)); features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false)); features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false)); - if (android.media.codec.Flags.hlgEditing()) { + if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) { features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true)); } - if (android.media.codec.Flags.regionOfInterest()) { + if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) { features.add(new Feature(FEATURE_Roi, (1 << 7), true)); } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index d6d74e8a087a..94fce797f5d6 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -122,9 +122,6 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], - - // TODO(b/330503129) Workaround build breakage. - lto_O0: true, } cc_library_shared { diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index 7caa9e4863f9..cf5059ceb3c9 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -44,7 +44,4 @@ cc_library_shared { "-Wunreachable-code", "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], - - // TODO(b/330503129) Workaround LTO build breakage. - lto_O0: true, } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 976a3ad69901..bcc737a351a9 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -30,6 +30,8 @@ import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -46,6 +48,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -135,10 +138,10 @@ public class PackageUtil { static final class AppSnippet implements Parcelable { @NonNull public CharSequence label; - @Nullable public Drawable icon; + @NonNull public Drawable icon; public int iconSize; - AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) { + AppSnippet(@NonNull CharSequence label, @NonNull Drawable icon, Context context) { this.label = label; this.icon = icon; final ActivityManager am = context.getSystemService(ActivityManager.class); @@ -147,14 +150,15 @@ public class PackageUtil { private AppSnippet(Parcel in) { label = in.readString(); - Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class); + byte[] b = in.readBlob(); + Bitmap bmp = BitmapFactory.decodeByteArray(b, 0, b.length); icon = new BitmapDrawable(Resources.getSystem(), bmp); iconSize = in.readInt(); } @Override public String toString() { - return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]"; + return "AppSnippet[" + label + " (has icon)]"; } @Override @@ -165,16 +169,18 @@ public class PackageUtil { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(label.toString()); + Bitmap bmp = getBitmapFromDrawable(icon); - dest.writeParcelable(bmp, 0); + dest.writeBlob(getBytesFromBitmap(bmp)); + bmp.recycle(); + dest.writeInt(iconSize); } private Bitmap getBitmapFromDrawable(Drawable drawable) { // Create an empty bitmap with the dimensions of our drawable final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); + drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); // Associate it with a canvas. This canvas will draw the icon on the bitmap final Canvas canvas = new Canvas(bmp); // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the @@ -192,6 +198,23 @@ public class PackageUtil { return bmp; } + private byte[] getBytesFromBitmap(Bitmap bmp) { + ByteArrayOutputStream baos = null; + try { + baos = new ByteArrayOutputStream(); + bmp.compress(CompressFormat.PNG, 100, baos); + } finally { + try { + if (baos != null) { + baos.close(); + } + } catch (IOException e) { + Log.e(LOG_TAG, "ByteArrayOutputStream was not closed"); + } + } + return baos.toByteArray(); + } + public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() { public AppSnippet createFromParcel(Parcel in) { return new AppSnippet(in); diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml new file mode 100644 index 000000000000..c7fbb5f0374b --- /dev/null +++ b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml @@ -0,0 +1,26 @@ +<?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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="@color/settingslib_materialColorOnSurfaceVariant" + android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> +</vector> diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml new file mode 100644 index 000000000000..a2b964882c7c --- /dev/null +++ b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:background="?android:attr/selectableItemBackground" + android:orientation="vertical" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="56dp" + android:gravity="start|top" + android:orientation="horizontal" + android:paddingEnd="12dp" + android:paddingTop="16dp" + android:paddingBottom="4dp"> + <ImageView + android:id="@android:id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:paddingTop="16dp" + android:paddingBottom="8dp" + android:textColor="@color/settingslib_materialColorOnSurfaceVariant" + android:hyphenationFrequency="normalFast" + android:lineBreakWordStyle="phrase" + android:ellipsize="marquee" /> + + <com.android.settingslib.widget.LinkTextView + android:id="@+id/settingslib_learn_more" + android:text="@string/settingslib_learn_more_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:paddingBottom="8dp" + android:clickable="true" + android:visibility="gone" /> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml new file mode 100644 index 000000000000..fff41c3583f2 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml @@ -0,0 +1,24 @@ +<?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. + --> +<resources> + <style name="TextAppearance.TopIntroText" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item> + </style> + +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 8a5dfefa18c3..69e4bd7afdd2 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1030,13 +1030,6 @@ <!-- Settings item title to select whether to disable cache for transcoding. [CHAR LIMIT=85] --> <string name="transcode_disable_cache">Disable transcoding cache</string> - <!-- Developer settings title: widevine settings screen. [CHAR LIMIT=50] --> - <string name="widevine_settings_title">Widevine settings</string> - <!-- Developer settings title: select whether to enable Force L3 fallback. [CHAR LIMIT=50] --> - <string name="force_l3_fallback_title">Force L3 fallback</string> - <!-- Developer settings summary: select whether to enable Force L3 fallback.[CHAR LIMIT=NONE] --> - <string name="force_l3_fallback_summary">Select to force L3 fallback</string> - <!-- Services settings screen, setting option name for the user to go to the screen to view running services --> <string name="runningservices_settings_title">Running services</string> <!-- Services settings screen, setting option summary for the user to go to the screen to view running services --> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 2889ce26d65d..56118dae3f96 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -51,9 +51,11 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.text.format.Formatter; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; @@ -990,11 +992,22 @@ public class ApplicationsState { apps = new ArrayList<>(mAppEntries); } + ArrayMap<UserHandle, Boolean> profileHideInQuietModeStatus = new ArrayMap<>(); ArrayList<AppEntry> filteredApps = new ArrayList<>(); if (DEBUG) { Log.i(TAG, "Rebuilding..."); } for (AppEntry entry : apps) { + if (android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + UserHandle userHandle = UserHandle.of(UserHandle.getUserId(entry.info.uid)); + if (!profileHideInQuietModeStatus.containsKey(userHandle)) { + profileHideInQuietModeStatus.put( + userHandle, isHideInQuietEnabledForProfile(mUm, userHandle)); + } + filter.refreshAppEntryOnRebuild( + entry, profileHideInQuietModeStatus.get(userHandle)); + } if (entry != null && (filter == null || filter.filterApp(entry))) { synchronized (mEntriesMap) { if (DEBUG_LOCKING) { @@ -1648,6 +1661,11 @@ public class ApplicationsState { */ public boolean isHomeApp; + /** + * Whether the app should be hidden for user when quiet mode is enabled. + */ + public boolean hideInQuietMode; + public String getNormalizedLabel() { if (normalizedLabel != null) { return normalizedLabel; @@ -1691,6 +1709,7 @@ public class ApplicationsState { UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid)); mProfileType = userInfo.userType; this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid); + hideInQuietMode = shouldHideInQuietMode(um, info.uid); } public boolean isClonedProfile() { @@ -1800,12 +1819,32 @@ public class ApplicationsState { this.labelDescription = this.label; } } + + /** + * Returns true if profile is in quiet mode and the profile should not be visible when the + * quiet mode is enabled, false otherwise. + */ + private boolean shouldHideInQuietMode(@NonNull UserManager userManager, int uid) { + if (android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + UserHandle userHandle = UserHandle.of(UserHandle.getUserId(uid)); + return isHideInQuietEnabledForProfile(userManager, userHandle); + } + return false; + } } private static boolean hasFlag(int flags, int flag) { return (flags & flag) != 0; } + private static boolean isHideInQuietEnabledForProfile( + UserManager userManager, UserHandle userHandle) { + return userManager.isQuietModeEnabled(userHandle) + && userManager.getUserProperties(userHandle).getShowInQuietMode() + == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN; + } + /** * Compare by label, then package name, then uid. */ @@ -1868,6 +1907,15 @@ public class ApplicationsState { } boolean filterApp(AppEntry info); + + /** + * Updates AppEntry based on whether quiet mode is enabled and should not be + * visible for the corresponding profile. + */ + default void refreshAppEntryOnRebuild( + @NonNull AppEntry appEntry, + boolean hideInQuietMode) { + } } public static final AppFilter FILTER_PERSONAL = new AppFilter() { @@ -2010,6 +2058,25 @@ public class ApplicationsState { } }; + public static final AppFilter FILTER_ENABLED_NOT_QUIET = new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(@NonNull AppEntry entry) { + return entry.info.enabled && !AppUtils.isInstant(entry.info) + && !entry.hideInQuietMode; + } + + @Override + public void refreshAppEntryOnRebuild( + @NonNull AppEntry appEntry, + boolean hideInQuietMode) { + appEntry.hideInQuietMode = hideInQuietMode; + } + }; + public static final AppFilter FILTER_EVERYTHING = new AppFilter() { @Override public void init() { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java index fe83ffb094e2..b9748883b25d 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java @@ -267,6 +267,16 @@ public class ApplicationsStateTest { } @Test + public void testEnabledFilterNotQuietRejectsInstantApp() { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE); + mEntry.info.enabled = true; + assertThat(ApplicationsState.FILTER_ENABLED_NOT_QUIET.filterApp(mEntry)).isTrue(); + when(mEntry.info.isInstantApp()).thenReturn(true); + assertThat(ApplicationsState.FILTER_ENABLED_NOT_QUIET.filterApp(mEntry)).isFalse(); + } + + @Test public void testFilterWithDomainUrls() { mEntry.info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS; // should included updated system apps diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index f70ad9ed58b0..5f3d1eafb4fb 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -62,6 +62,7 @@ import org.junit.AfterClass; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -452,6 +453,8 @@ public class AccessibilityMenuServiceTest { } @Test + @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. " + + "Coverage is low-impact.") public void testOnScreenLock_cannotOpenMenu() throws Throwable { closeScreen(); wakeUpScreen(); diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 503779c1801b..ed8027756f47 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -482,7 +482,7 @@ private fun EmptyStateCta( Card( modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding), colors = CardDefaults.cardColors(containerColor = Color.Transparent), - border = BorderStroke(3.dp, colors.primaryFixedDim), + border = BorderStroke(3.dp, colors.secondary), shape = RoundedCornerShape(size = 80.dp) ) { Column( @@ -495,7 +495,7 @@ private fun EmptyStateCta( text = stringResource(R.string.title_for_empty_state_cta), style = MaterialTheme.typography.displaySmall, textAlign = TextAlign.Center, - color = colors.secondaryFixed, + color = colors.secondary, ) Row( modifier = Modifier.fillMaxWidth(), @@ -505,8 +505,8 @@ private fun EmptyStateCta( modifier = Modifier.height(56.dp), colors = ButtonDefaults.buttonColors( - containerColor = colors.primaryFixed, - contentColor = colors.onPrimaryFixed, + containerColor = colors.primary, + contentColor = colors.onPrimary, ), onClick = { viewModel.onOpenWidgetEditor( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index c5c48e6d4b6b..fcd77609768e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -114,6 +114,11 @@ fun SceneScope.CollapsedShadeHeader( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { + val isDisabled by viewModel.isDisabled.collectAsState() + if (isDisabled) { + return + } + val formatProgress = animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress) .unsafeCompositionState(initialValue = 0f) @@ -251,6 +256,11 @@ fun SceneScope.ExpandedShadeHeader( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { + val isDisabled by viewModel.isDisabled.collectAsState() + if (isDisabled) { + return + } + val formatProgress = animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress) .unsafeCompositionState(initialValue = 1f) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 25c649a7d4fb..e086cda2f793 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -27,7 +27,9 @@ import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet import android.util.MathUtils.constrainedMap +import android.util.TypedValue import android.view.View +import android.view.View.MeasureSpec.EXACTLY import android.widget.TextView import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting @@ -42,6 +44,7 @@ import java.io.PrintWriter import java.util.Calendar import java.util.Locale import java.util.TimeZone +import kotlin.math.min /** * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) @@ -85,6 +88,8 @@ class AnimatableClockView @JvmOverloads constructor( private var textAnimator: TextAnimator? = null private var onTextAnimatorInitialized: Runnable? = null + // last text size which is not constrained by view height + private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) } @@ -188,6 +193,11 @@ class AnimatableClockView @JvmOverloads constructor( @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure") + if (migratedClocks && !isSingleLineInternal && + MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { + setTextSize(TypedValue.COMPLEX_UNIT_PX, + min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)) + } super.onMeasure(widthMeasureSpec, heightMeasureSpec) val animator = textAnimator if (animator == null) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 61089049bf89..ef385673c950 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository @@ -97,6 +98,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { ShadeHeaderViewModel( applicationScope = testScope.backgroundScope, context = context, + shadeInteractor = kosmos.shadeInteractor, mobileIconsInteractor = mobileIconsInteractor, mobileIconsViewModel = mobileIconsViewModel, privacyChipInteractor = kosmos.privacyChipInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 05c81496c3dc..efbdb7d466d1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -242,6 +242,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ShadeHeaderViewModel( applicationScope = testScope.backgroundScope, context = context, + shadeInteractor = kosmos.shadeInteractor, mobileIconsInteractor = mobileIconsInteractor, mobileIconsViewModel = mobileIconsViewModel, privacyChipInteractor = kosmos.privacyChipInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 062741df01cf..4c573d3b83ea 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -13,6 +13,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel @@ -69,6 +70,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { ShadeHeaderViewModel( applicationScope = testScope.backgroundScope, context = context, + shadeInteractor = kosmos.shadeInteractor, mobileIconsInteractor = mobileIconsInteractor, mobileIconsViewModel = mobileIconsViewModel, privacyChipInteractor = kosmos.privacyChipInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index d1c4ec3ddacf..f90a3b14f968 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -110,6 +110,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ShadeHeaderViewModel( applicationScope = testScope.backgroundScope, context = context, + shadeInteractor = kosmos.shadeInteractor, mobileIconsInteractor = mobileIconsInteractor, mobileIconsViewModel = mobileIconsViewModel, privacyChipInteractor = kosmos.privacyChipInteractor, @@ -280,17 +281,17 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun upTransitionSceneKey_customizing_noTransition() = - testScope.runTest { - val destinationScenes by collectLastValue(underTest.destinationScenes) - - qsSceneAdapter.setCustomizing(true) - assertThat( - destinationScenes!! - .keys - .filterIsInstance<Swipe>() - .filter { it.direction == SwipeDirection.Up } - ).isEmpty() - } + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + + qsSceneAdapter.setCustomizing(true) + assertThat( + destinationScenes!!.keys.filterIsInstance<Swipe>().filter { + it.direction == SwipeDirection.Up + } + ) + .isEmpty() + } @Test fun shadeMode() = diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index c99cb39f91bf..83658d3c1984 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -129,6 +129,11 @@ public interface BcSmartspaceDataPlugin extends Plugin { void setDozeAmount(float amount); /** + * Set if the screen is on. + */ + default void setScreenOn(boolean screenOn) {} + + /** * Set if dozing is true or false */ default void setDozing(boolean dozing) {} diff --git a/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml new file mode 100644 index 000000000000..edfbe7bed588 --- /dev/null +++ b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml @@ -0,0 +1,25 @@ +<?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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="@*android:dimen/rounded_corner_radius" /> + <!-- Since the device corners are not perfectly rounded, we create the stroke with offset + to fill up the space between border and device corner --> + <stroke + android:color="@color/magnification_border_color" + android:width="@dimen/magnifier_border_width_fullscreen_with_offset"/> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/fullscreen_magnification_border.xml b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml new file mode 100644 index 000000000000..5f738c041f54 --- /dev/null +++ b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml @@ -0,0 +1,22 @@ +<?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. + --> +<View xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/magnification_fullscreen_border" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="false" + android:background="@drawable/accessibility_fullscreen_magnification_border_background"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e004ee9fa157..29f4942e8b4a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1266,6 +1266,8 @@ <dimen name="magnifier_corner_radius">28dp</dimen> <dimen name="magnifier_edit_corner_radius">16dp</dimen> <dimen name="magnifier_edit_outer_corner_radius">18dp</dimen> + <dimen name="magnifier_border_width_fullscreen_with_offset">12dp</dimen> + <dimen name="magnifier_border_width_fullscreen">6dp</dimen> <dimen name="magnifier_border_width">8dp</dimen> <dimen name="magnifier_stroke_width">2dp</dimen> <dimen name="magnifier_edit_dash_gap">20dp</dimen> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 751a3f8458bd..68d2eb358105 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -22,6 +22,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import android.annotation.TargetApi; import android.content.Context; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; @@ -138,11 +139,15 @@ public class Utilities { /** @return whether or not {@param context} represents that of a large screen device or not */ @TargetApi(Build.VERSION_CODES.R) public static boolean isLargeScreen(Context context) { - final WindowManager windowManager = context.getSystemService(WindowManager.class); + return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources()); + } + + /** @return whether or not {@param context} represents that of a large screen device or not */ + public static boolean isLargeScreen(WindowManager windowManager, Resources resources) { final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()), - context.getResources().getConfiguration().densityDpi); + resources.getConfiguration().densityDpi); return smallestWidth >= TABLET_MIN_DPS; } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 1a9b01fd4996..7e94804908b6 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -29,8 +29,8 @@ import com.android.systemui.res.R; import com.android.systemui.util.InitializationChecker; import com.android.wm.shell.dagger.WMShellConcurrencyModule; import com.android.wm.shell.keyguard.KeyguardTransitions; +import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.sysui.ShellInterface; -import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; import java.util.concurrent.ExecutionException; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java new file mode 100644 index 000000000000..af8149f59a16 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java @@ -0,0 +1,140 @@ +/* + * 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.accessibility; + +import static android.view.WindowManager.LayoutParams; + +import android.annotation.UiContext; +import android.content.Context; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Region; +import android.view.AttachedSurfaceControl; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; + +import androidx.annotation.UiThread; + +import com.android.systemui.res.R; + +import java.util.function.Supplier; + +class FullscreenMagnificationController { + + private final Context mContext; + private final AccessibilityManager mAccessibilityManager; + private final WindowManager mWindowManager; + private Supplier<SurfaceControlViewHost> mScvhSupplier; + private SurfaceControlViewHost mSurfaceControlViewHost; + private Rect mWindowBounds; + private SurfaceControl.Transaction mTransaction; + private View mFullscreenBorder = null; + private int mBorderOffset; + private final int mDisplayId; + private static final Region sEmptyRegion = new Region(); + + FullscreenMagnificationController( + @UiContext Context context, + AccessibilityManager accessibilityManager, + WindowManager windowManager, + Supplier<SurfaceControlViewHost> scvhSupplier) { + mContext = context; + mAccessibilityManager = accessibilityManager; + mWindowManager = windowManager; + mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); + mTransaction = new SurfaceControl.Transaction(); + mScvhSupplier = scvhSupplier; + mBorderOffset = mContext.getResources().getDimensionPixelSize( + R.dimen.magnifier_border_width_fullscreen_with_offset) + - mContext.getResources().getDimensionPixelSize( + R.dimen.magnifier_border_width_fullscreen); + mDisplayId = mContext.getDisplayId(); + } + + @UiThread + void onFullscreenMagnificationActivationChanged(boolean activated) { + if (activated) { + createFullscreenMagnificationBorder(); + } else { + removeFullscreenMagnificationBorder(); + } + } + + @UiThread + private void removeFullscreenMagnificationBorder() { + if (mSurfaceControlViewHost != null) { + mSurfaceControlViewHost.release(); + mSurfaceControlViewHost = null; + } + + if (mFullscreenBorder != null) { + mFullscreenBorder = null; + } + } + + /** + * Since the device corners are not perfectly rounded, we would like to create a thick stroke, + * and set negative offset to the border view to fill up the spaces between the border and the + * device corners. + */ + @UiThread + private void createFullscreenMagnificationBorder() { + mFullscreenBorder = LayoutInflater.from(mContext) + .inflate(R.layout.fullscreen_magnification_border, null); + mSurfaceControlViewHost = mScvhSupplier.get(); + mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams()); + + SurfaceControl surfaceControl = mSurfaceControlViewHost + .getSurfacePackage().getSurfaceControl(); + + mTransaction + .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset) + .setLayer(surfaceControl, Integer.MAX_VALUE) + .show(surfaceControl) + .apply(); + + mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl); + + applyTouchableRegion(); + } + + private LayoutParams getBorderLayoutParams() { + LayoutParams params = new LayoutParams( + mWindowBounds.width() + 2 * mBorderOffset, + mWindowBounds.height() + 2 * mBorderOffset, + LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, + LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSPARENT); + params.setTrustedOverlay(); + return params; + } + + private void applyTouchableRegion() { + // Sometimes this can get posted and run after deleteWindowMagnification() is called. + if (mFullscreenBorder == null) return; + + AttachedSurfaceControl surfaceControl = mSurfaceControlViewHost.getRootSurfaceControl(); + + // The touchable region of the mFullscreenBorder will be empty since we are going to allow + // all touch events to go through this view. + surfaceControl.setTouchableRegion(sEmptyRegion); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index 88fa2de186d0..70165f377a7c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -34,6 +34,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; +import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IMagnificationConnection; @@ -134,6 +135,37 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @VisibleForTesting DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier; + private static class FullscreenMagnificationControllerSupplier extends + DisplayIdIndexSupplier<FullscreenMagnificationController> { + + private final Context mContext; + + FullscreenMagnificationControllerSupplier(Context context, Handler handler, + DisplayManager displayManager, SysUiState sysUiState, + SecureSettings secureSettings) { + super(displayManager); + mContext = context; + } + + @Override + protected FullscreenMagnificationController createInstance(Display display) { + final Context windowContext = mContext.createWindowContext(display, + TYPE_ACCESSIBILITY_OVERLAY, /* options */ null); + Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost( + mContext, mContext.getDisplay(), new InputTransferToken(), TAG); + windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI); + return new FullscreenMagnificationController( + windowContext, + windowContext.getSystemService(AccessibilityManager.class), + windowContext.getSystemService(WindowManager.class), + scvhSupplier); + } + } + + @VisibleForTesting + DisplayIdIndexSupplier<FullscreenMagnificationController> + mFullscreenMagnificationControllerSupplier; + private static class SettingsSupplier extends DisplayIdIndexSupplier<MagnificationSettingsController> { @@ -185,6 +217,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context, mHandler, mWindowMagnifierCallback, displayManager, sysUiState, secureSettings); + mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier( + context, mHandler, displayManager, sysUiState, secureSettings); mMagnificationSettingsSupplier = new SettingsSupplier(context, mMagnificationSettingsControllerCallback, displayManager, secureSettings); @@ -273,8 +307,13 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { } } + @MainThread void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) { - // Do nothing + final FullscreenMagnificationController fullscreenMagnificationController = + mFullscreenMagnificationControllerSupplier.get(displayId); + if (fullscreenMagnificationController != null) { + fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated); + } } @MainThread diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java index 35fe6b14ee28..c464c82cb90f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java @@ -22,7 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.annotation.IntDef; import android.content.ComponentCallbacks; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; @@ -37,7 +36,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; -import com.android.settingslib.Utils; import com.android.systemui.res.R; import java.lang.annotation.Retention; @@ -171,9 +169,9 @@ class MenuMessageView extends LinearLayout implements mTextView.setTextColor(textColor); mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL); - final ColorStateList colorAccent = Utils.getColorAccent(getContext()); mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo)); mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize); - mUndoButton.setTextColor(colorAccent); + mUndoButton.setTextColor(textColor); + mUndoButton.setAllCaps(true); } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 3b0c281a7057..e104166935c1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -36,11 +36,11 @@ import com.android.wm.shell.keyguard.KeyguardTransitions; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; +import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.taskview.TaskViewFactory; -import com.android.wm.shell.transition.ShellTransitions; import dagger.BindsInstance; import dagger.Subcomponent; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index dfec771ced26..e04a0e542855 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -23,7 +23,6 @@ import androidx.annotation.Nullable; import com.android.systemui.SystemUIInitializer; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.desktopmode.DesktopMode; @@ -32,11 +31,12 @@ import com.android.wm.shell.keyguard.KeyguardTransitions; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; +import com.android.wm.shell.shared.ShellTransitions; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.taskview.TaskViewFactory; -import com.android.wm.shell.transition.ShellTransitions; import dagger.BindsInstance; import dagger.Subcomponent; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 19a44ccdb554..d33e7fffef98 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -394,10 +394,6 @@ object Flags { // TODO(b/251205791): Tracking Bug @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips") - /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */ - @JvmField - val MULTI_DISPLAY_SCREENSHOT = releasedFlag("multi_display_screenshot") - // 1400 - columbus // TODO(b/254512756): Tracking Bug val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 3134e35a92e3..6b53f4ed554f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -84,8 +84,8 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; import com.android.systemui.settings.DisplayTracker; import com.android.wm.shell.shared.CounterRotator; +import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.TransitionUtil; -import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 53c81e537708..cf8358203b6c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -20,7 +20,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.WallpaperManager -import android.content.Context +import android.content.res.Resources import android.graphics.Matrix import android.graphics.Rect import android.os.DeadObjectException @@ -32,16 +32,18 @@ import android.view.RemoteAnimationTarget import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier import android.view.View +import android.view.WindowManager import androidx.annotation.VisibleForTesting import androidx.core.math.MathUtils import com.android.app.animation.Interpolators import com.android.internal.R import com.android.keyguard.KeyguardClockSwitchController import com.android.keyguard.KeyguardViewController +import com.android.systemui.Flags.fastUnlockTransition import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.Flags.fastUnlockTransition import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.shared.recents.utilities.Utilities import com.android.systemui.shared.system.ActivityManagerWrapper @@ -145,17 +147,18 @@ const val SURFACE_BEHIND_FADE_OUT_START_DELAY_MS = 0L */ @SysUISingleton class KeyguardUnlockAnimationController @Inject constructor( - private val context: Context, - private val keyguardStateController: KeyguardStateController, - private val + private val windowManager: WindowManager, + @Main private val resources: Resources, + private val keyguardStateController: KeyguardStateController, + private val keyguardViewMediator: Lazy<KeyguardViewMediator>, - private val keyguardViewController: KeyguardViewController, - private val featureFlags: FeatureFlags, - private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>, - private val statusBarStateController: SysuiStatusBarStateController, - private val notificationShadeWindowController: NotificationShadeWindowController, - private val powerManager: PowerManager, - private val wallpaperManager: WallpaperManager + private val keyguardViewController: KeyguardViewController, + private val featureFlags: FeatureFlags, + private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>, + private val statusBarStateController: SysuiStatusBarStateController, + private val notificationShadeWindowController: NotificationShadeWindowController, + private val powerManager: PowerManager, + private val wallpaperManager: WallpaperManager ) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() { interface KeyguardUnlockAnimationListener { @@ -399,7 +402,7 @@ class KeyguardUnlockAnimationController @Inject constructor( keyguardStateController.addCallback(this) roundedCornerRadius = - context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat() + resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat() } /** @@ -438,7 +441,7 @@ class KeyguardUnlockAnimationController @Inject constructor( Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " + "${!notificationShadeWindowController.isLaunchingActivity}") Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}") - Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(context)}") + Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}") } /** @@ -1100,7 +1103,7 @@ class KeyguardUnlockAnimationController @Inject constructor( // We don't do the shared element on large screens because the smartspace has to fly across // large distances, which is distracting. - if (Utilities.isLargeScreen(context)) { + if (Utilities.isLargeScreen(windowManager, resources)) { return false } @@ -1180,8 +1183,8 @@ class KeyguardUnlockAnimationController @Inject constructor( companion object { - fun isFoldable(context: Context): Boolean { - return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty() + fun isFoldable(resources: Resources): Boolean { + return resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty() } } } 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 881467ff2724..4a09939d0aba 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 @@ -25,6 +25,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.GONE +import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP @@ -160,7 +161,7 @@ constructor( constraints.apply { connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START) connect(R.id.lockscreen_clock_view_large, END, guideline, END) - connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP) + connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP) var largeClockTopMargin = context.resources.getDimensionPixelSize(R.dimen.status_bar_height) + context.resources.getDimensionPixelSize( @@ -172,7 +173,7 @@ constructor( connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT) - constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT) + constrainHeight(R.id.lockscreen_clock_view_large, MATCH_CONSTRAINT) constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT) constrainHeight( R.id.lockscreen_clock_view, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt index 3081f89d0cb9..922997d08c25 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -18,32 +18,11 @@ package com.android.systemui.screenshot import android.util.Log import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.app.tracing.coroutines.launch -import kotlinx.coroutines.CoroutineScope -import java.util.function.Consumer -import javax.inject.Inject - -/** Processes a screenshot request sent from [ScreenshotHelper]. */ -interface ScreenshotRequestProcessor { - /** - * Inspects the incoming ScreenshotData, potentially modifying it based upon policy. - * - * @param screenshot the screenshot to process - */ - suspend fun process(screenshot: ScreenshotData): ScreenshotData -} /** Implementation of [ScreenshotRequestProcessor] */ -@SysUISingleton -class RequestProcessor -@Inject -constructor( +class RequestProcessor( private val capture: ImageCapture, private val policy: ScreenshotPolicy, - /** For the Java Async version, to invoke the callback. */ - @Application private val mainScope: CoroutineScope ) : ScreenshotRequestProcessor { override suspend fun process(screenshot: ScreenshotData): ScreenshotData { @@ -78,24 +57,6 @@ constructor( return result } - - /** - * Note: This is for compatibility with existing Java. Prefer the suspending function when - * calling from a Coroutine context. - * - * @param screenshot the screenshot to process - * @param callback the callback to provide the processed screenshot, invoked from the main - * thread - */ - fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) { - mainScope.launch({ "$TAG#processAsync" }) { - val result = process(screenshot) - callback.accept(result) - } - } } private const val TAG = "RequestProcessor" - -/** Exception thrown by [RequestProcessor] if something goes wrong. */ -class RequestProcessorException(message: String) : IllegalStateException(message) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt index d874eb68460b..4079abe41a7d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt @@ -105,7 +105,7 @@ internal constructor( /** Factory for [ScreenshotNotificationsController]. */ @AssistedFactory - interface Factory { - fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController + fun interface Factory { + fun create(displayId: Int): ScreenshotNotificationsController } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt index d5ab3066bfde..c8067df114fc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext /** Provides state from the main SystemUI process on behalf of the Screenshot process. */ -internal class ScreenshotProxyService +class ScreenshotProxyService @Inject constructor( private val mExpansionMgr: ShadeExpansionStateManager, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt new file mode 100644 index 000000000000..796457d88cc2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.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.systemui.screenshot + +/** Processes a screenshot request sent from [ScreenshotHelper]. */ +interface ScreenshotRequestProcessor { + /** + * Inspects the incoming ScreenshotData, potentially modifying it based upon policy. + * + * @param screenshot the screenshot to process + */ + suspend fun process(screenshot: ScreenshotData): ScreenshotData +} + +/** Exception thrown by [RequestProcessor] if something goes wrong. */ +class RequestProcessorException(message: String) : IllegalStateException(message) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index bc3375502dd4..92d3e550dc0f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -5,7 +5,6 @@ import android.os.Trace import android.util.Log import android.view.Display import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE -import com.android.app.tracing.coroutines.launch import com.android.internal.logging.UiEventLogger import com.android.internal.util.ScreenshotRequest import com.android.systemui.dagger.SysUISingleton @@ -18,6 +17,23 @@ import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +interface TakeScreenshotExecutor { + suspend fun executeScreenshots( + screenshotRequest: ScreenshotRequest, + onSaved: (Uri) -> Unit, + requestCallback: RequestCallback + ) + fun onCloseSystemDialogsReceived() + fun removeWindows() + fun onDestroy() + fun executeScreenshotsAsync( + screenshotRequest: ScreenshotRequest, + onSaved: Consumer<Uri>, + requestCallback: RequestCallback + ) +} /** * Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the @@ -26,7 +42,7 @@ import kotlinx.coroutines.flow.first * Captures a screenshot for each [Display] available. */ @SysUISingleton -class TakeScreenshotExecutor +class TakeScreenshotExecutorImpl @Inject constructor( private val screenshotControllerFactory: ScreenshotController.Factory, @@ -35,7 +51,7 @@ constructor( private val screenshotRequestProcessor: ScreenshotRequestProcessor, private val uiEventLogger: UiEventLogger, private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory, -) { +) : TakeScreenshotExecutor { private val displays = displayRepository.displays private val screenshotControllers = mutableMapOf<Int, ScreenshotController>() @@ -47,7 +63,7 @@ constructor( * [onSaved] is invoked only on the default display result. [RequestCallback.onFinish] is * invoked only when both screenshot UIs are removed. */ - suspend fun executeScreenshots( + override suspend fun executeScreenshots( screenshotRequest: ScreenshotRequest, onSaved: (Uri) -> Unit, requestCallback: RequestCallback @@ -128,12 +144,8 @@ constructor( } } - /** - * Propagates the close system dialog signal to all controllers. - * - * TODO(b/295143676): Move the receiver in this class once the flag is flipped. - */ - fun onCloseSystemDialogsReceived() { + /** Propagates the close system dialog signal to all controllers. */ + override fun onCloseSystemDialogsReceived() { screenshotControllers.forEach { (_, screenshotController) -> if (!screenshotController.isPendingSharedTransition) { screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) @@ -142,7 +154,7 @@ constructor( } /** Removes all screenshot related windows. */ - fun removeWindows() { + override fun removeWindows() { screenshotControllers.forEach { (_, screenshotController) -> screenshotController.removeWindow() } @@ -151,7 +163,7 @@ constructor( /** * Destroys the executor. Afterwards, this class is not expected to work as intended anymore. */ - fun onDestroy() { + override fun onDestroy() { screenshotControllers.forEach { (_, screenshotController) -> screenshotController.onDestroy() } @@ -171,12 +183,12 @@ constructor( } /** For java compatibility only. see [executeScreenshots] */ - fun executeScreenshotsAsync( + override fun executeScreenshotsAsync( screenshotRequest: ScreenshotRequest, onSaved: Consumer<Uri>, requestCallback: RequestCallback ) { - mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") { + mainScope.launch { executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 9cf347bc8569..2187b51be7dc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -21,13 +21,11 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI; -import static com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE; import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED; -import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; import android.annotation.MainThread; import android.app.Service; @@ -50,24 +48,19 @@ import android.util.Log; import android.view.Display; import android.widget.Toast; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotRequest; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; -import javax.inject.Provider; public class TakeScreenshotService extends Service { private static final String TAG = logTag(TakeScreenshotService.class); - private final ScreenshotController mScreenshot; - private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; private final UiEventLogger mUiEventLogger; @@ -75,27 +68,20 @@ public class TakeScreenshotService extends Service { private final Handler mHandler; private final Context mContext; private final @Background Executor mBgExecutor; - private final RequestProcessor mProcessor; - private final FeatureFlags mFeatureFlags; + private final TakeScreenshotExecutor mTakeScreenshotExecutor; + @SuppressWarnings("deprecation") private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) { + if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { if (DEBUG_DISMISS) { Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS"); } - if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { - // TODO(b/295143676): move receiver inside executor when the flag is enabled. - mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived(); - } else if (!mScreenshot.isPendingSharedTransition()) { - mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER); - } + mTakeScreenshotExecutor.onCloseSystemDialogsReceived(); } } }; - private final Provider<TakeScreenshotExecutor> mTakeScreenshotExecutor; - /** Informs about coarse grained state of the Controller. */ public interface RequestCallback { @@ -111,12 +97,14 @@ public class TakeScreenshotService extends Service { } @Inject - public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory, - UserManager userManager, DevicePolicyManager devicePolicyManager, + public TakeScreenshotService( + UserManager userManager, + DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger, ScreenshotNotificationsController.Factory notificationsControllerFactory, - Context context, @Background Executor bgExecutor, FeatureFlags featureFlags, - RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) { + Context context, + @Background Executor bgExecutor, + TakeScreenshotExecutor takeScreenshotExecutor) { if (DEBUG_SERVICE) { Log.d(TAG, "new " + this); } @@ -127,15 +115,7 @@ public class TakeScreenshotService extends Service { mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY); mContext = context; mBgExecutor = bgExecutor; - mFeatureFlags = featureFlags; - mProcessor = processor; mTakeScreenshotExecutor = takeScreenshotExecutor; - if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { - mScreenshot = null; - } else { - mScreenshot = screenshotControllerFactory.create( - Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false); - } } @Override @@ -161,11 +141,7 @@ public class TakeScreenshotService extends Service { if (DEBUG_SERVICE) { Log.d(TAG, "onUnbind"); } - if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { - mTakeScreenshotExecutor.get().removeWindows(); - } else { - mScreenshot.removeWindow(); - } + mTakeScreenshotExecutor.removeWindows(); unregisterReceiver(mCloseSystemDialogs); return false; } @@ -173,11 +149,7 @@ public class TakeScreenshotService extends Service { @Override public void onDestroy() { super.onDestroy(); - if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { - mTakeScreenshotExecutor.get().onDestroy(); - } else { - mScreenshot.onDestroy(); - } + mTakeScreenshotExecutor.onDestroy(); if (DEBUG_SERVICE) { Log.d(TAG, "onDestroy"); } @@ -190,6 +162,7 @@ public class TakeScreenshotService extends Service { mReplyTo = replyTo; } + @Override public void reportError() { reportUri(mReplyTo, null); sendComplete(mReplyTo); @@ -214,7 +187,6 @@ public class TakeScreenshotService extends Service { } @MainThread - @VisibleForTesting void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved, RequestCallback callback) { // If the storage for this user is locked, we have no place to store @@ -245,36 +217,9 @@ public class TakeScreenshotService extends Service { } Log.d(TAG, "Processing screenshot data"); - - - if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { - mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback); - return; - } - // TODO(b/295143676): Delete the following after the flag is released. - try { - ScreenshotData screenshotData = ScreenshotData.fromRequest( - request, Display.DEFAULT_DISPLAY); - mProcessor.processAsync(screenshotData, (data) -> - dispatchToController(data, onSaved, callback)); - - } catch (IllegalStateException e) { - Log.e(TAG, "Failed to process screenshot request!", e); - logFailedRequest(request); - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - callback.reportError(); - } + mTakeScreenshotExecutor.executeScreenshotsAsync(request, onSaved, callback); } - // TODO(b/295143676): Delete this. - private void dispatchToController(ScreenshotData screenshot, - Consumer<Uri> uriConsumer, RequestCallback callback) { - mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0, - screenshot.getPackageNameString()); - Log.d(TAG, "Screenshot request: " + screenshot); - mScreenshot.handleScreenshot(screenshot, uriConsumer, callback); - } private void logFailedRequest(ScreenshotRequest request) { ComponentName topComponent = request.getTopComponent(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 2ce6d8380e36..6ff0fdaeab0f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -26,21 +26,22 @@ import com.android.systemui.screenshot.DefaultScreenshotActionsProvider; import com.android.systemui.screenshot.ImageCapture; import com.android.systemui.screenshot.ImageCaptureImpl; import com.android.systemui.screenshot.LegacyScreenshotViewProxy; -import com.android.systemui.screenshot.RequestProcessor; import com.android.systemui.screenshot.ScreenshotActionsProvider; import com.android.systemui.screenshot.ScreenshotPolicy; import com.android.systemui.screenshot.ScreenshotPolicyImpl; -import com.android.systemui.screenshot.ScreenshotProxyService; -import com.android.systemui.screenshot.ScreenshotRequestProcessor; import com.android.systemui.screenshot.ScreenshotShelfViewProxy; import com.android.systemui.screenshot.ScreenshotSoundController; import com.android.systemui.screenshot.ScreenshotSoundControllerImpl; import com.android.systemui.screenshot.ScreenshotSoundProvider; import com.android.systemui.screenshot.ScreenshotSoundProviderImpl; import com.android.systemui.screenshot.ScreenshotViewProxy; +import com.android.systemui.screenshot.TakeScreenshotExecutor; +import com.android.systemui.screenshot.TakeScreenshotExecutorImpl; import com.android.systemui.screenshot.TakeScreenshotService; import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService; import com.android.systemui.screenshot.appclips.AppClipsService; +import com.android.systemui.screenshot.policy.ScreenshotPolicyModule; +import com.android.systemui.screenshot.proxy.SystemUiProxyModule; import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel; import dagger.Binds; @@ -52,7 +53,7 @@ import dagger.multibindings.IntoMap; /** * Defines injectable resources for Screenshots */ -@Module +@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class}) public abstract class ScreenshotModule { @Binds @@ -61,9 +62,9 @@ public abstract class ScreenshotModule { abstract Service bindTakeScreenshotService(TakeScreenshotService service); @Binds - @IntoMap - @ClassKey(ScreenshotProxyService.class) - abstract Service bindScreenshotProxyService(ScreenshotProxyService service); + @SysUISingleton + abstract TakeScreenshotExecutor bindTakeScreenshotExecutor( + TakeScreenshotExecutorImpl impl); @Binds abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl); @@ -82,10 +83,6 @@ public abstract class ScreenshotModule { abstract Service bindAppClipsService(AppClipsService service); @Binds - abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor( - RequestProcessor requestProcessor); - - @Binds abstract ScreenshotSoundProvider bindScreenshotSoundProvider( ScreenshotSoundProviderImpl screenshotSoundProviderImpl); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt new file mode 100644 index 000000000000..38016ad6b708 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt @@ -0,0 +1,31 @@ +/* + * 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.screenshot.data.model + +/** The profile type of a user. */ +enum class ProfileType { + /** The user is not a profile. */ + NONE, + /** Private space user */ + PRIVATE, + /** Managed (work) profile. */ + WORK, + /** Cloned apps user */ + CLONE, + /** Communal profile */ + COMMUNAL, +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt new file mode 100644 index 000000000000..2c6e4fe765e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt @@ -0,0 +1,29 @@ +/* + * 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.screenshot.data.repository + +import android.annotation.UserIdInt +import com.android.systemui.screenshot.data.model.ProfileType + +/** A facility for checking user profile types. */ +fun interface ProfileTypeRepository { + /** + * Returns the profile type when [userId] refers to a profile user. If the profile type is + * unknown or not a profile user, [ProfileType.NONE] is returned. + */ + suspend fun getProfileType(@UserIdInt userId: Int): ProfileType +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt new file mode 100644 index 000000000000..42ad21bdf5e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt @@ -0,0 +1,58 @@ +/* + * 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. + */ +@file:SuppressLint("MissingPermission") + +package com.android.systemui.screenshot.data.repository + +import android.annotation.SuppressLint +import android.annotation.UserIdInt +import android.os.UserManager +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.screenshot.data.model.ProfileType +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext + +/** Fetches profile types from [UserManager] as needed, caching results for a given user. */ +class ProfileTypeRepositoryImpl +@Inject +constructor( + private val userManager: UserManager, + @Background private val background: CoroutineDispatcher +) : ProfileTypeRepository { + /** Cache to avoid repeated requests to IActivityTaskManager for the same userId */ + private val cache = mutableMapOf<Int, ProfileType>() + private val mutex = Mutex() + + override suspend fun getProfileType(@UserIdInt userId: Int): ProfileType { + return mutex.withLock { + cache[userId] + ?: withContext(background) { + val userType = userManager.getUserInfo(userId).userType + when (userType) { + UserManager.USER_TYPE_PROFILE_MANAGED -> ProfileType.WORK + UserManager.USER_TYPE_PROFILE_PRIVATE -> ProfileType.PRIVATE + UserManager.USER_TYPE_PROFILE_CLONE -> ProfileType.CLONE + UserManager.USER_TYPE_PROFILE_COMMUNAL -> ProfileType.COMMUNAL + else -> ProfileType.NONE + } + } + .also { cache[userId] = it } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt new file mode 100644 index 000000000000..39b07e3a396a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt @@ -0,0 +1,48 @@ +/* + * 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.screenshot.policy + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.screenshot.ImageCapture +import com.android.systemui.screenshot.RequestProcessor +import com.android.systemui.screenshot.ScreenshotPolicy +import com.android.systemui.screenshot.ScreenshotRequestProcessor +import com.android.systemui.screenshot.data.repository.ProfileTypeRepository +import com.android.systemui.screenshot.data.repository.ProfileTypeRepositoryImpl +import dagger.Binds +import dagger.Module +import dagger.Provides +import javax.inject.Provider + +@Module +interface ScreenshotPolicyModule { + + @Binds + @SysUISingleton + fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository + + companion object { + @Provides + @SysUISingleton + fun bindScreenshotRequestProcessor( + imageCapture: ImageCapture, + policyProvider: Provider<ScreenshotPolicy>, + ): ScreenshotRequestProcessor { + return RequestProcessor(imageCapture, policyProvider.get()) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt new file mode 100644 index 000000000000..e3eb3c4de80a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt @@ -0,0 +1,36 @@ +/* + * 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.screenshot.proxy + +/** + * Provides a mechanism to interact with the main SystemUI process. + * + * ScreenshotService runs in an isolated process. Because of this, interactions with an outside + * component with shared state must be accessed through this proxy to reach the correct instance. + * + * TODO: Rename and relocate 'ScreenshotProxyService' to this package and remove duplicate clients. + */ +interface SystemUiProxy { + /** Indicate if the notification shade is "open"... (not in the fully collapsed position) */ + suspend fun isNotificationShadeExpanded(): Boolean + + /** + * Request keyguard dismissal, raising keyguard credential entry if required and waits for + * completion. + */ + suspend fun dismissKeyguard() +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt new file mode 100644 index 000000000000..dcf58bde5f70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt @@ -0,0 +1,70 @@ +/* + * 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.screenshot.proxy + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.util.Log +import com.android.internal.infra.ServiceConnector +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.screenshot.IOnDoneCallback +import com.android.systemui.screenshot.IScreenshotProxy +import com.android.systemui.screenshot.ScreenshotProxyService +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.CompletableDeferred + +private const val TAG = "SystemUiProxy" + +/** An implementation of [SystemUiProxy] using [ScreenshotProxyService]. */ +class SystemUiProxyClient @Inject constructor(@Application context: Context) : SystemUiProxy { + @SuppressLint("ImplicitSamInstance") + private val proxyConnector: ServiceConnector<IScreenshotProxy> = + ServiceConnector.Impl( + context, + Intent(context, ScreenshotProxyService::class.java), + Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, + context.userId, + IScreenshotProxy.Stub::asInterface + ) + + override suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k -> + proxyConnector + .postForResult { it.isNotificationShadeExpanded } + .whenComplete { expanded, error -> + error?.also { Log.wtf(TAG, "isNotificationShadeExpanded", it) } + k.resume(expanded ?: false) + } + } + + override suspend fun dismissKeyguard() { + val completion = CompletableDeferred<Unit>() + val onDoneBinder = + object : IOnDoneCallback.Stub() { + override fun onDone(success: Boolean) { + completion.complete(Unit) + } + } + if (proxyConnector.run { it.dismissKeyguard(onDoneBinder) }) { + completion.await() + } else { + Log.wtf(TAG, "Keyguard dismissal request failed") + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt new file mode 100644 index 000000000000..4dd5cc4b55b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt @@ -0,0 +1,38 @@ +/* + * 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.screenshot.proxy + +import android.app.Service +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.screenshot.ScreenshotProxyService +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface SystemUiProxyModule { + + @Binds + @IntoMap + @ClassKey(ScreenshotProxyService::class) + fun bindScreenshotProxyService(service: ScreenshotProxyService): Service + + @Binds + @SysUISingleton + fun bindSystemUiProxy(systemUiProxyClient: SystemUiProxyClient): SystemUiProxy +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index c93ef65ae7c6..e4f5aeb5028c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -334,6 +334,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final ScrimController mScrimController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final TapAgainViewController mTapAgainViewController; + private final ShadeHeaderController mShadeHeaderController; private final boolean mVibrateOnOpening; private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private final FlingAnimationUtils mFlingAnimationUtilsClosing; @@ -730,6 +731,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump FragmentService fragmentService, IStatusBarService statusBarService, ContentResolver contentResolver, + ShadeHeaderController shadeHeaderController, ScreenOffAnimationController screenOffAnimationController, LockscreenGestureLogger lockscreenGestureLogger, ShadeExpansionStateManager shadeExpansionStateManager, @@ -874,6 +876,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mSplitShadeEnabled = mSplitShadeStateController.shouldUseSplitNotificationShade(mResources); mView.setWillNotDraw(!DEBUG_DRAWABLE); + mShadeHeaderController = shadeHeaderController; mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; mAnimateBack = predictiveBackAnimateShade(); @@ -1107,6 +1110,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } mTapAgainViewController.init(); + mShadeHeaderController.init(); + mShadeHeaderController.setShadeCollapseAction( + () -> collapse(/* delayed= */ false , /* speedUpFactor= */ 1.0f)); mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView)); mNotificationPanelUnfoldAnimationController.ifPresent(controller -> controller.setup(mNotificationContainerParent)); @@ -4968,6 +4974,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } + if (DeviceEntryUdfpsRefactor.isEnabled() + && mAlternateBouncerInteractor.isVisibleState()) { + // never send touches to shade if the alternate bouncer is showing + return false; + } + if (event.getAction() == MotionEvent.ACTION_DOWN) { if (event.getDownTime() == mLastTouchDownTime) { // An issue can occur when swiping down after unlock, where multiple down diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index cde45f2060e5..0de3c10329e3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -29,6 +29,9 @@ interface ShadeInteractor : BaseShadeInteractor { /** Emits true if the shade is currently allowed and false otherwise. */ val isShadeEnabled: StateFlow<Boolean> + /** Emits true if QS is currently allowed and false otherwise. */ + val isQsEnabled: StateFlow<Boolean> + /** Whether either the shade or QS is fully expanded. */ val isAnyFullyExpanded: StateFlow<Boolean> diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt index 5fbd2cfaec79..883ef97a6aad 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -29,6 +29,7 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { private val inactiveFlowBoolean = MutableStateFlow(false) private val inactiveFlowFloat = MutableStateFlow(0f) override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean + override val isQsEnabled: StateFlow<Boolean> = inactiveFlowBoolean override val shadeExpansion: StateFlow<Float> = inactiveFlowFloat override val qsExpansion: StateFlow<Float> = inactiveFlowFloat override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index 7e14e58b4c9c..d68e28c930f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -60,6 +60,11 @@ constructor( .distinctUntilChanged() .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + override val isQsEnabled: StateFlow<Boolean> = + disableFlagsRepository.disableFlags + .map { it.isQuickSettingsEnabled() } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + override val isAnyFullyExpanded: StateFlow<Boolean> = anyExpansion .map { it >= 1f } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt index 60810a06249f..d8216dcfdec6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt @@ -23,8 +23,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.ShadeTouchLog -import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeHeaderController import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.shared.model.ShadeMode @@ -46,25 +44,15 @@ constructor( private val configurationRepository: ConfigurationRepository, private val shadeRepository: ShadeRepository, private val controller: SplitShadeStateController, - private val shadeController: ShadeController, - private val shadeHeaderController: ShadeHeaderController, private val scrimShadeTransitionController: ScrimShadeTransitionController, ) : CoreStartable { override fun start() { hydrateShadeMode() logTouchesTo(touchLog) - initHeaderController() scrimShadeTransitionController.init() } - private fun initHeaderController() { - shadeHeaderController.init() - shadeHeaderController.shadeCollapseAction = Runnable { - shadeController.animateCollapseShade() - } - } - private fun hydrateShadeMode() { applicationScope.launch { configurationRepository.onAnyConfigurationChange diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt index 1191c0f247f9..72a9c8d01554 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.privacy.PrivacyItem import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import java.util.Date @@ -53,6 +54,7 @@ class ShadeHeaderViewModel constructor( @Application private val applicationScope: CoroutineScope, context: Context, + shadeInteractor: ShadeInteractor, mobileIconsInteractor: MobileIconsInteractor, val mobileIconsViewModel: MobileIconsViewModel, private val privacyChipInteractor: PrivacyChipInteractor, @@ -85,6 +87,12 @@ constructor( /** Whether or not the privacy chip is enabled in the device privacy config. */ val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled + /** Whether or not the Shade Header should be disabled based on disableFlags. */ + val isDisabled: StateFlow<Boolean> = + shadeInteractor.isQsEnabled + .map { !it } + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false) + private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm) private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year) private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern)) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 0fd05550dbe3..c29a64ed61b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -36,6 +36,7 @@ import android.util.Log import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup +import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.settingslib.Utils import com.android.systemui.Dumpable @@ -45,6 +46,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin @@ -95,6 +97,7 @@ constructor( private val deviceProvisionedController: DeviceProvisionedController, private val bypassController: KeyguardBypassController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val wakefulnessLifecycle: WakefulnessLifecycle, private val dumpManager: DumpManager, private val execution: Execution, @Main private val uiExecutor: Executor, @@ -123,7 +126,7 @@ constructor( private val recentSmartspaceData: Deque<List<SmartspaceTarget>> = LinkedList() // Smartspace can be used on multiple displays, such as when the user casts their screen - private var smartspaceViews = mutableSetOf<SmartspaceView>() + @VisibleForTesting var smartspaceViews = mutableSetOf<SmartspaceView>() private var regionSamplers = mutableMapOf<SmartspaceView, RegionSampler>() @@ -272,6 +275,18 @@ constructor( } } + // TODO(b/331451011): Refactor to viewmodel and use interactor pattern. + private val wakefulnessLifecycleObserver = + object : WakefulnessLifecycle.Observer { + override fun onStartedWakingUp() { + smartspaceViews.forEach { it.setScreenOn(true) } + } + + override fun onFinishedGoingToSleep() { + smartspaceViews.forEach { it.setScreenOn(false) } + } + } + init { deviceProvisionedController.addCallback(deviceProvisionedListener) dumpManager.registerDumpable(this) @@ -451,6 +466,7 @@ constructor( configurationController.addCallback(configChangeListener) statusBarStateController.addCallback(statusBarStateListener) bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener) + wakefulnessLifecycle.addObserver(wakefulnessLifecycleObserver) datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } @@ -493,6 +509,7 @@ constructor( configurationController.removeCallback(configChangeListener) statusBarStateController.removeCallback(statusBarStateListener) bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener) + wakefulnessLifecycle.removeObserver(wakefulnessLifecycleObserver) session = null datePlugin?.registerSmartspaceEventNotifier(null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index ad3012e54d75..e93c0f6a5559 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -52,6 +52,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.QuickSettingsController; @@ -279,7 +280,9 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba } } - mShadeHeaderController.disable(state1, state2, animate); + if (!SceneContainerFlag.isEnabled()) { + mShadeHeaderController.disable(state1, state2, animate); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 9633cb085afa..579d43b5fd05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -453,6 +453,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene setTopMargin(0); if (grandParent != null) grandParent.setClipChildren(true); setVisibility(GONE); + setAlpha(1f); if (mWrapper != null) { mWrapper.setRemoteInputVisible(false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java new file mode 100644 index 000000000000..12f334ba08bb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java @@ -0,0 +1,97 @@ +/* + * 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.accessibility; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControlViewHost; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; +import android.window.InputTransferToken; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Supplier; + +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class FullscreenMagnificationControllerTest extends SysuiTestCase { + + private FullscreenMagnificationController mFullscreenMagnificationController; + private SurfaceControlViewHost mSurfaceControlViewHost; + + @Before + public void setUp() { + getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost = + new SurfaceControlViewHost(mContext, mContext.getDisplay(), + new InputTransferToken(), "FullscreenMagnification")); + + Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost; + + mFullscreenMagnificationController = new FullscreenMagnificationController( + mContext, + mContext.getSystemService(AccessibilityManager.class), + mContext.getSystemService(WindowManager.class), + scvhSupplier); + } + + @After + public void tearDown() { + getInstrumentation().runOnMainSync( + () -> mFullscreenMagnificationController + .onFullscreenMagnificationActivationChanged(false)); + } + + @Test + public void onFullscreenMagnificationActivationChange_activated_visibleBorder() { + getInstrumentation().runOnMainSync( + () -> mFullscreenMagnificationController + .onFullscreenMagnificationActivationChanged(true) + ); + + // Wait for Rects updated. + waitForIdleSync(); + assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue(); + } + + @Test + public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() { + getInstrumentation().runOnMainSync( + () -> { + mFullscreenMagnificationController + .onFullscreenMagnificationActivationChanged(true); + mFullscreenMagnificationController + .onFullscreenMagnificationActivationChanged(false); + } + ); + + assertThat(mSurfaceControlViewHost.getView()).isNull(); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index bd49927c8b53..41d5d5d919e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -71,6 +71,8 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Mock private WindowMagnificationController mWindowMagnificationController; @Mock + private FullscreenMagnificationController mFullscreenMagnificationController; + @Mock private MagnificationSettingsController mMagnificationSettingsController; @Mock private ModeSwitchesController mModeSwitchesController; @@ -105,6 +107,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase { mMagnification.mWindowMagnificationControllerSupplier = new FakeWindowMagnificationControllerSupplier( mContext.getSystemService(DisplayManager.class)); + mMagnification.mFullscreenMagnificationControllerSupplier = + new FakeFullscreenMagnificationControllerSupplier( + mContext.getSystemService(DisplayManager.class)); mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier( mContext.getSystemService(DisplayManager.class)); @@ -124,6 +129,15 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } @Test + public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException { + mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true); + waitForIdleSync(); + + verify(mFullscreenMagnificationController) + .onFullscreenMagnificationActivationChanged(eq(true)); + } + + @Test public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException { mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, mAnimationCallback); @@ -215,6 +229,20 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } } + + private class FakeFullscreenMagnificationControllerSupplier extends + DisplayIdIndexSupplier<FullscreenMagnificationController> { + + FakeFullscreenMagnificationControllerSupplier(DisplayManager displayManager) { + super(displayManager); + } + + @Override + protected FullscreenMagnificationController createInstance(Display display) { + return mFullscreenMagnificationController; + } + } + private class FakeSettingsSupplier extends DisplayIdIndexSupplier<MagnificationSettingsController> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 51828c91de4b..6ebda4db808f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -14,6 +14,7 @@ import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewRootImpl +import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.Flags @@ -52,6 +53,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock + private lateinit var windowManager: WindowManager + @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -99,7 +102,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) keyguardUnlockAnimationController = KeyguardUnlockAnimationController( - context, keyguardStateController, { keyguardViewMediator }, keyguardViewController, + windowManager, context.resources, + keyguardStateController, { keyguardViewMediator }, keyguardViewController, featureFlags, { biometricUnlockController }, statusBarStateController, notificationShadeWindowController, powerManager, wallpaperManager ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt index 7e41745936f8..0847f01f12d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -30,8 +30,6 @@ import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import com.android.internal.util.ScreenshotRequest import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Test @@ -44,35 +42,8 @@ class RequestProcessorTest { private val component = ComponentName("android.test", "android.test.Component") private val bounds = Rect(25, 25, 75, 75) - private val scope = CoroutineScope(Dispatchers.Unconfined) private val policy = FakeScreenshotPolicy() - /** Tests the Java-compatible function wrapper, ensures callback is invoked. */ - @Test - fun testProcessAsync_ScreenshotData() { - val request = - ScreenshotData.fromRequest( - ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_KEY_OTHER) - .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) - .build() - ) - val processor = RequestProcessor(imageCapture, policy, scope) - - var result: ScreenshotData? = null - var callbackCount = 0 - val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData -> - result = processedRequest - callbackCount++ - } - - // runs synchronously, using Unconfined Dispatcher - processor.processAsync(request, callback) - - // Callback invoked once returning the same request (no changes) - assertThat(callbackCount).isEqualTo(1) - assertThat(result).isEqualTo(request) - } - @Test fun testFullScreenshot() = runBlocking { // Indicate that the primary content belongs to a normal user @@ -84,7 +55,7 @@ class RequestProcessorTest { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build() - val processor = RequestProcessor(imageCapture, policy, scope) + val processor = RequestProcessor(imageCapture, policy) val processedData = processor.process(ScreenshotData.fromRequest(request)) @@ -109,7 +80,7 @@ class RequestProcessorTest { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() - val processor = RequestProcessor(imageCapture, policy, scope) + val processor = RequestProcessor(imageCapture, policy) val processedData = processor.process(ScreenshotData.fromRequest(request)) @@ -136,7 +107,7 @@ class RequestProcessorTest { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() - val processor = RequestProcessor(imageCapture, policy, scope) + val processor = RequestProcessor(imageCapture, policy) Assert.assertThrows(IllegalStateException::class.java) { runBlocking { processor.process(ScreenshotData.fromRequest(request)) } @@ -146,7 +117,7 @@ class RequestProcessorTest { @Test fun testProvidedImageScreenshot() = runBlocking { val bounds = Rect(50, 50, 150, 150) - val processor = RequestProcessor(imageCapture, policy, scope) + val processor = RequestProcessor(imageCapture, policy) policy.setManagedProfile(USER_ID, false) @@ -171,7 +142,7 @@ class RequestProcessorTest { @Test fun testProvidedImageScreenshot_managedProfile() = runBlocking { val bounds = Rect(50, 50, 150, 150) - val processor = RequestProcessor(imageCapture, policy, scope) + val processor = RequestProcessor(imageCapture, policy) // Indicate that the screenshot belongs to a manged profile policy.setManagedProfile(USER_ID, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index 0baee5dd0e9d..c900463c7159 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -57,7 +57,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { private val eventLogger = UiEventLoggerFake() private val screenshotExecutor = - TakeScreenshotExecutor( + TakeScreenshotExecutorImpl( controllerFactory, fakeDisplayRepository, testScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index f3809aad4de3..0776aa7d1845 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -20,134 +20,97 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN import android.app.admin.DevicePolicyResourcesManager import android.content.ComponentName +import android.net.Uri import android.os.UserHandle import android.os.UserManager import android.testing.AndroidTestingRunner -import android.view.Display import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN -import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.ScreenshotRequest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback -import com.android.systemui.util.mockito.any -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 com.google.common.truth.Truth.assertWithMessage import java.util.function.Consumer +import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.isNull -import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doThrow -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) -@SmallTest class TakeScreenshotServiceTest : SysuiTestCase() { - private val application = mock<Application>() - private val controller = mock<ScreenshotController>() - private val controllerFactory = mock<ScreenshotController.Factory>() - private val takeScreenshotExecutor = mock<TakeScreenshotExecutor>() - private val userManager = mock<UserManager>() - private val requestProcessor = mock<RequestProcessor>() - private val devicePolicyManager = mock<DevicePolicyManager>() - private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>() - private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>() + private val userManager = mock<UserManager> { on { isUserUnlocked } doReturn (true) } + + private val devicePolicyResourcesManager = + mock<DevicePolicyResourcesManager> { + on { getString(eq(SCREENSHOT_BLOCKED_BY_ADMIN), /* defaultStringLoader= */ any()) } + .doReturn("SCREENSHOT_BLOCKED_BY_ADMIN") + } + + private val devicePolicyManager = + mock<DevicePolicyManager> { + on { resources } doReturn (devicePolicyResourcesManager) + on { getScreenCaptureDisabled(/* admin= */ isNull(), eq(UserHandle.USER_ALL)) } + .doReturn(false) + } + private val notificationsController = mock<ScreenshotNotificationsController>() - private val callback = mock<RequestCallback>() + private val notificationsControllerFactory = + ScreenshotNotificationsController.Factory { notificationsController } + private val executor = FakeScreenshotExecutor() + private val callback = FakeRequestCallback() private val eventLogger = UiEventLoggerFake() - private val flags = FakeFeatureFlags() private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java) - private lateinit var service: TakeScreenshotService - - @Before - fun setUp() { - flags.set(MULTI_DISPLAY_SCREENSHOT, false) - whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager) - whenever( - devicePolicyManager.getScreenCaptureDisabled( - /* admin component (null: any admin) */ isNull(), - eq(UserHandle.USER_ALL) - ) - ) - .thenReturn(false) - whenever(userManager.isUserUnlocked).thenReturn(true) - whenever(controllerFactory.create(any(), any())).thenReturn(controller) - whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController) - - // Stub request processor as a synchronous no-op for tests with the flag enabled - doAnswer { - val request: ScreenshotData = it.getArgument(0) as ScreenshotData - val consumer: Consumer<ScreenshotData> = it.getArgument(1) - consumer.accept(request) - } - .whenever(requestProcessor) - .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any()) - - service = createService() - } - @Test fun testServiceLifecycle() { + val service = createService() service.onCreate() service.onBind(null /* unused: Intent */) + assertThat(executor.windowsPresent).isTrue() service.onUnbind(null /* unused: Intent */) - verify(controller, times(1)).removeWindow() + assertThat(executor.windowsPresent).isFalse() service.onDestroy() - verify(controller, times(1)).onDestroy() + assertThat(executor.destroyed).isTrue() } @Test fun takeScreenshotFullscreen() { + val service = createService() + val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) .setTopComponent(topComponent) .build() service.handleRequest(request, { /* onSaved */}, callback) + assertWithMessage("request received by executor").that(executor.requestReceived).isNotNull() - verify(controller, times(1)) - .handleScreenshot( - eq(ScreenshotData.fromRequest(request, Display.DEFAULT_DISPLAY)), - /* onSavedListener = */ any(), - /* requestCallback = */ any() - ) - - assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) - val logEvent = eventLogger.get(0) - - assertEquals( - "Expected SCREENSHOT_REQUESTED UiEvent", - logEvent.eventId, - SCREENSHOT_REQUESTED_KEY_OTHER.id - ) - assertEquals( - "Expected supplied package name", - topComponent.packageName, - eventLogger.get(0).packageName - ) + assertWithMessage("request received by executor") + .that(ScreenshotData.fromRequest(executor.requestReceived!!)) + .isEqualTo(ScreenshotData.fromRequest(request)) } @Test fun takeScreenshotFullscreen_userLocked() { - whenever(userManager.isUserUnlocked).thenReturn(false) + val service = createService() + whenever(userManager.isUserUnlocked).doReturn(false) val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) @@ -157,100 +120,51 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.handleRequest(request, { /* onSaved */}, callback) verify(notificationsController, times(1)).notifyScreenshotError(anyInt()) - verify(callback, times(1)).reportError() - verifyZeroInteractions(controller) - assertEquals("Expected two UiEvents", 2, eventLogger.numLogs()) + assertWithMessage("callback errorReported").that(callback.errorReported).isTrue() + + assertWithMessage("UiEvent count").that(eventLogger.numLogs()).isEqualTo(2) + val requestEvent = eventLogger.get(0) - assertEquals( - "Expected SCREENSHOT_REQUESTED_* UiEvent", - SCREENSHOT_REQUESTED_KEY_OTHER.id, - requestEvent.eventId - ) - assertEquals( - "Expected supplied package name", - topComponent.packageName, - requestEvent.packageName - ) + assertWithMessage("request UiEvent id") + .that(requestEvent.eventId) + .isEqualTo(SCREENSHOT_REQUESTED_KEY_OTHER.id) + + assertWithMessage("topComponent package name") + .that(requestEvent.packageName) + .isEqualTo(topComponent.packageName) + val failureEvent = eventLogger.get(1) - assertEquals( - "Expected SCREENSHOT_CAPTURE_FAILED UiEvent", - SCREENSHOT_CAPTURE_FAILED.id, - failureEvent.eventId - ) - assertEquals( - "Expected supplied package name", - topComponent.packageName, - failureEvent.packageName - ) + assertWithMessage("failure UiEvent id") + .that(failureEvent.eventId) + .isEqualTo(SCREENSHOT_CAPTURE_FAILED.id) + + assertWithMessage("Supplied package name") + .that(failureEvent.packageName) + .isEqualTo(topComponent.packageName) } @Test fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() { - whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL))) - .thenReturn(true) + val service = createService() whenever( - devicePolicyResourcesManager.getString( - eq(SCREENSHOT_BLOCKED_BY_ADMIN), - /* Supplier<String> */ - any(), + devicePolicyManager.getScreenCaptureDisabled( + /* admin= */ isNull(), + eq(UserHandle.USER_ALL) ) ) - .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN") - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) - .setTopComponent(topComponent) - .build() - - service.handleRequest(request, { /* onSaved */}, callback) - - // error shown: Toast.makeText(...).show(), untestable - verify(callback, times(1)).reportError() - verifyZeroInteractions(controller) - assertEquals("Expected two UiEvents", 2, eventLogger.numLogs()) - val requestEvent = eventLogger.get(0) - assertEquals( - "Expected SCREENSHOT_REQUESTED_* UiEvent", - SCREENSHOT_REQUESTED_KEY_OTHER.id, - requestEvent.eventId - ) - assertEquals( - "Expected supplied package name", - topComponent.packageName, - requestEvent.packageName - ) - val failureEvent = eventLogger.get(1) - assertEquals( - "Expected SCREENSHOT_CAPTURE_FAILED UiEvent", - SCREENSHOT_CAPTURE_FAILED.id, - failureEvent.eventId - ) - assertEquals( - "Expected supplied package name", - topComponent.packageName, - failureEvent.packageName - ) - } + .doReturn(true) - @Test - fun takeScreenshot_workProfile_nullBitmap() { val request = ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) .setTopComponent(topComponent) .build() - doThrow(IllegalStateException::class.java) - .whenever(requestProcessor) - .processAsync(any(ScreenshotData::class.java), any()) - service.handleRequest(request, { /* onSaved */}, callback) + assertThat(callback.errorReported).isTrue() + assertWithMessage("Expected two UiEvents").that(eventLogger.numLogs()).isEqualTo(2) - verify(callback, times(1)).reportError() - verify(notificationsController, times(1)).notifyScreenshotError(anyInt()) - verifyZeroInteractions(controller) - assertEquals("Expected two UiEvents", 2, eventLogger.numLogs()) val requestEvent = eventLogger.get(0) assertEquals( "Expected SCREENSHOT_REQUESTED_* UiEvent", @@ -275,71 +189,70 @@ class TakeScreenshotServiceTest : SysuiTestCase() { ) } - @Test - fun takeScreenshotFullScreen_multiDisplayFlagEnabled_takeScreenshotExecutor() { - flags.set(MULTI_DISPLAY_SCREENSHOT, true) - service = createService() - - val request = - ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER) - .setTopComponent(topComponent) - .build() - - service.handleRequest(request, { /* onSaved */}, callback) - - verifyZeroInteractions(controller) - verify(takeScreenshotExecutor, times(1)).executeScreenshotsAsync(any(), any(), any()) - - assertEquals("Expected one UiEvent", 0, eventLogger.numLogs()) - } - - @Test - fun testServiceLifecycle_multiDisplayScreenshotFlagEnabled() { - flags.set(MULTI_DISPLAY_SCREENSHOT, true) - service = createService() - - service.onCreate() - service.onBind(null /* unused: Intent */) - - service.onUnbind(null /* unused: Intent */) - verify(takeScreenshotExecutor, times(1)).removeWindows() - - service.onDestroy() - verify(takeScreenshotExecutor, times(1)).onDestroy() - } - - @Test - fun constructor_MultiDisplayFlagOn_screenshotControllerNotCreated() { - flags.set(MULTI_DISPLAY_SCREENSHOT, true) - clearInvocations(controllerFactory) - - service = createService() - - verifyZeroInteractions(controllerFactory) - } - private fun createService(): TakeScreenshotService { val service = TakeScreenshotService( - controllerFactory, userManager, devicePolicyManager, eventLogger, notificationsControllerFactory, mContext, Runnable::run, - flags, - requestProcessor, - { takeScreenshotExecutor }, + executor ) + service.attach( mContext, /* thread = */ null, /* className = */ null, /* token = */ null, - application, + mock<Application>(), /* activityManager = */ null ) return service } } + +internal class FakeRequestCallback : RequestCallback { + var errorReported = false + var finished = false + override fun reportError() { + errorReported = true + } + + override fun onFinish() { + finished = true + } +} + +internal class FakeScreenshotExecutor : TakeScreenshotExecutor { + var requestReceived: ScreenshotRequest? = null + var windowsPresent = true + var destroyed = false + override fun onCloseSystemDialogsReceived() {} + override suspend fun executeScreenshots( + screenshotRequest: ScreenshotRequest, + onSaved: (Uri) -> Unit, + requestCallback: RequestCallback, + ) { + requestReceived = screenshotRequest + } + + override fun removeWindows() { + windowsPresent = false + } + + override fun onDestroy() { + destroyed = true + } + + override fun executeScreenshotsAsync( + screenshotRequest: ScreenshotRequest, + onSaved: Consumer<Uri>, + requestCallback: RequestCallback, + ) { + runBlocking { + executeScreenshots(screenshotRequest, { onSaved.accept(it) }, requestCallback) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 56e61e4c6442..e957ca2eca3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -711,6 +711,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFragmentService, mStatusBarService, mContentResolver, + mShadeHeaderController, mScreenOffAnimationController, mLockscreenGestureLogger, mShadeExpansionStateManager, @@ -896,8 +897,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mConfigurationController.onConfigurationChanged(configuration); } - protected void onTouchEvent(MotionEvent ev) { - mTouchHandler.onTouch(mView, ev); + protected boolean onTouchEvent(MotionEvent ev) { + return mTouchHandler.onTouch(mView, ev); } protected void setDozing(boolean dozing, boolean dozingAlwaysOn) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 6d5d5be8ae57..29a92d969ca4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -364,6 +364,24 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void alternateBouncerVisible_onTouchEvent_notHandled() { + mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); + // GIVEN alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // WHEN touch DOWN event received; THEN touch is NOT handled + assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */))).isFalse(); + + // WHEN touch MOVE event received; THEN touch is NOT handled + assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, + 0 /* metaState */))).isFalse(); + + } + + @Test public void test_onTouchEvent_startTracking() { // GIVEN device is NOT pulsing mNotificationPanelViewController.setPulsing(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index a5f3f57f5be6..5abad61d90ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin @@ -180,6 +181,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private lateinit var dateSmartspaceView: SmartspaceView private lateinit var weatherSmartspaceView: SmartspaceView private lateinit var smartspaceView: SmartspaceView + private lateinit var wakefulnessLifecycle: WakefulnessLifecycle private val clock = FakeSystemClock() private val executor = FakeExecutor(clock) @@ -225,6 +227,14 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { setAllowPrivateNotifications(userHandleSecondary, true) setShowNotifications(userHandlePrimary, true) + // Use the real wakefulness lifecycle instead of a mock + wakefulnessLifecycle = WakefulnessLifecycle( + context, + /* wallpaper= */ null, + clock, + dumpManager + ) + controller = LockscreenSmartspaceController( context, featureFlags, @@ -240,6 +250,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { deviceProvisionedController, keyguardBypassController, keyguardUpdateMonitor, + wakefulnessLifecycle, dumpManager, execution, executor, @@ -773,6 +784,38 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(configurationController, never()).addCallback(any()) } + @Test + fun testWakefulnessLifecycleDispatch_wake_setsSmartspaceScreenOnTrue() { + // Connect session + connectSession() + + // Add mock views + val mockSmartspaceView = mock(SmartspaceView::class.java) + controller.smartspaceViews.add(mockSmartspaceView) + + // Initiate wakefulness change + wakefulnessLifecycle.dispatchStartedWakingUp(0) + + // Verify smartspace views receive screen on + verify(mockSmartspaceView).setScreenOn(true) + } + + @Test + fun testWakefulnessLifecycleDispatch_sleep_setsSmartspaceScreenOnFalse() { + // Connect session + connectSession() + + // Add mock views + val mockSmartspaceView = mock(SmartspaceView::class.java) + controller.smartspaceViews.add(mockSmartspaceView) + + // Initiate wakefulness change + wakefulnessLifecycle.dispatchFinishedGoingToSleep() + + // Verify smartspace views receive screen on + verify(mockSmartspaceView).setScreenOn(false) + } + private fun connectSession() { val dateView = controller.buildAndConnectDateView(fakeParent) dateSmartspaceView = dateView as SmartspaceView diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index c25978271b17..70afbd82df11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -67,10 +67,10 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.Dependency; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -448,6 +448,34 @@ public class RemoteInputViewTest extends SysuiTestCase { assertEquals(1f, fadeInView.getAlpha()); } + @Test + public void testUnanimatedFocusAfterDefocusAnimation() throws Exception { + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(); + RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); + bindController(view, row.getEntry()); + + FrameLayout parent = new FrameLayout(mContext); + parent.addView(view); + + // Play defocus animation + view.onDefocus(true /* animate */, false /* logClose */, null /* doAfterDefocus */); + mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD); + + // assert that RemoteInputView is no longer visible, but alpha is reset to 1f + assertEquals(View.GONE, view.getVisibility()); + assertEquals(1f, view.getAlpha()); + + // focus RemoteInputView without an animation + view.focus(); + // assert that RemoteInputView is visible, and alpha is 1f + assertEquals(View.VISIBLE, view.getVisibility()); + assertEquals(1f, view.getAlpha()); + } + // NOTE: because we're refactoring the RemoteInputView and moving logic into the // RemoteInputViewController, it's easiest to just test the system of the two classes together. @NonNull diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt index 65e04f4e476d..7dfa68605ef3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt @@ -22,9 +22,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.LogBuffer -import com.android.systemui.shade.ShadeHeaderController import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.shadeController import com.android.systemui.shade.transition.ScrimShadeTransitionController import com.android.systemui.statusbar.policy.splitShadeStateController import com.android.systemui.util.mockito.mock @@ -37,8 +35,6 @@ val Kosmos.shadeStartable by Fixture { configurationRepository = configurationRepository, shadeRepository = shadeRepository, controller = splitShadeStateController, - shadeHeaderController = mock<ShadeHeaderController>(), scrimShadeTransitionController = mock<ScrimShadeTransitionController>(), - shadeController = shadeController ) } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index fb2805574ff0..1f65e15c1bff 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -37,6 +37,8 @@ import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -695,6 +697,13 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect throw new IllegalArgumentException( bluetoothAddress + " is not a valid Bluetooth address"); } + final BluetoothManager bluetoothManager = + mContext.getSystemService(BluetoothManager.class); + final String bluetoothDeviceName = bluetoothManager == null ? null : + bluetoothManager.getAdapter().getBondedDevices().stream() + .filter(device -> device.getAddress().equalsIgnoreCase(bluetoothAddress)) + .map(BluetoothDevice::getName) + .findFirst().orElse(null); synchronized (mLock) { checkAccessibilityAccessLocked(); if (mBrailleDisplayConnection != null) { @@ -706,7 +715,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect connection.setTestData(mTestBrailleDisplays); } connection.connectLocked( - bluetoothAddress, BrailleDisplayConnection.BUS_BLUETOOTH, controller); + bluetoothAddress, + bluetoothDeviceName, + BrailleDisplayConnection.BUS_BLUETOOTH, + controller); } } @@ -763,7 +775,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect connection.setTestData(mTestBrailleDisplays); } connection.connectLocked( - usbSerialNumber, BrailleDisplayConnection.BUS_USB, controller); + usbSerialNumber, + usbDevice.getProductName(), + BrailleDisplayConnection.BUS_USB, + controller); } } diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java index 8b41873636a9..b0da3f014452 100644 --- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java @@ -24,6 +24,7 @@ import android.accessibilityservice.IBrailleDisplayConnection; import android.accessibilityservice.IBrailleDisplayController; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.PermissionManuallyEnforced; import android.annotation.RequiresNoPermission; import android.bluetooth.BluetoothDevice; @@ -33,6 +34,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; @@ -141,6 +143,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { @BusType int getDeviceBusType(@NonNull Path path); + + String getName(@NonNull Path path); } /** @@ -149,15 +153,19 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { * <p>If found, saves instance state for this connection and starts a thread to * read from the Braille display. * - * @param expectedUniqueId The expected unique ID of the device to connect, from - * {@link UsbDevice#getSerialNumber()} - * or {@link BluetoothDevice#getAddress()} - * @param expectedBusType The expected bus type from {@link BusType}. - * @param controller Interface containing oneway callbacks used to communicate with the - * {@link android.accessibilityservice.BrailleDisplayController}. + * @param expectedUniqueId The expected unique ID of the device to connect, from + * {@link UsbDevice#getSerialNumber()} or + * {@link BluetoothDevice#getAddress()}. + * @param expectedName The expected name of the device to connect, from + * {@link BluetoothDevice#getName()} or + * {@link UsbDevice#getProductName()}. + * @param expectedBusType The expected bus type from {@link BusType}. + * @param controller Interface containing oneway callbacks used to communicate with the + * {@link android.accessibilityservice.BrailleDisplayController}. */ void connectLocked( @NonNull String expectedUniqueId, + @Nullable String expectedName, @BusType int expectedBusType, @NonNull IBrailleDisplayController controller) { Objects.requireNonNull(expectedUniqueId); @@ -179,10 +187,20 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { unableToGetDescriptor = true; continue; } + final boolean matchesIdentifier; final String uniqueId = mScanner.getUniqueId(path); + if (uniqueId != null) { + matchesIdentifier = expectedUniqueId.equalsIgnoreCase(uniqueId); + } else { + // HIDIOCGRAWUNIQ was added in kernel version 5.7. + // If the device has an older kernel that does not support that ioctl then as a + // fallback we can check against the device name (from HIDIOCGRAWNAME). + final String name = mScanner.getName(path); + matchesIdentifier = !TextUtils.isEmpty(expectedName) && expectedName.equals(name); + } if (isBrailleDisplay(descriptor) && mScanner.getDeviceBusType(path) == expectedBusType - && expectedUniqueId.equalsIgnoreCase(uniqueId)) { + && matchesIdentifier) { result.add(Pair.create(path.toFile(), descriptor)); } } @@ -498,6 +516,12 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { Integer busType = readFromFileDescriptor(path, nativeInterface::getHidrawBusType); return busType != null ? busType : BUS_UNKNOWN; } + + @Override + public String getName(@NonNull Path path) { + Objects.requireNonNull(path); + return readFromFileDescriptor(path, nativeInterface::getHidrawName); + } }; } @@ -542,6 +566,12 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH) ? BUS_BLUETOOTH : BUS_USB; } + + @Override + public String getName(@NonNull Path path) { + return brailleDisplayMap.get(path).getString( + BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME); + } }; return mScanner; } @@ -579,6 +609,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { * @return the result of ioctl(HIDIOCGRAWINFO).bustype, or -1 if the ioctl fails. */ int getHidrawBusType(int fd); + + String getHidrawName(int fd); } /** Native interface that actually calls native HIDRAW ioctls. */ @@ -602,6 +634,11 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { public int getHidrawBusType(int fd) { return nativeGetHidrawBusType(fd); } + + @Override + public String getHidrawName(int fd) { + return nativeGetHidrawName(fd); + } } private static native int nativeGetHidrawDescSize(int fd); @@ -611,4 +648,6 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { private static native String nativeGetHidrawUniq(int fd); private static native int nativeGetHidrawBusType(int fd); + + private static native String nativeGetHidrawName(int fd); } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 993a1d5494a2..558e07f77656 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -53,8 +53,10 @@ import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_EXPLICITLY_REQUESTED; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER; +import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_RETRIGGER; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE; import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT; import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_AUTOFILL_PROVIDER; @@ -543,6 +545,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0); FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class); + mFillRequestEventLogger.maybeSetRequestTriggerReason( + TRIGGER_REASON_RETRIGGER); mAssistReceiver.processDelayedFillLocked(requestId, response); } } @@ -1268,7 +1272,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if(mPreviouslyFillDialogPotentiallyStarted) { mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER); } else { - mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER); + if ((flags & FLAG_MANUAL_REQUEST) != 0) { + mFillRequestEventLogger.maybeSetRequestTriggerReason( + TRIGGER_REASON_EXPLICITLY_REQUESTED); + } else { + mFillRequestEventLogger.maybeSetRequestTriggerReason( + TRIGGER_REASON_NORMAL_TRIGGER); + } } if (existingResponse != null) { setViewStatesLocked( diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index e3f16ae07202..253fe35a3e29 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -20,6 +20,11 @@ import static android.permission.flags.Flags.sensitiveNotificationAppProtection; import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS; import static android.view.flags.Flags.sensitiveContentAppProtection; +import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION; +import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS; +import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START; +import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -86,9 +91,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic private static class MediaProjectionSession { final int mUid; final long mSessionId; + final boolean mIsExempted; - MediaProjectionSession(int uid, long sessionId) { + MediaProjectionSession(int uid, boolean isExempted, long sessionId) { mUid = uid; + mIsExempted = isExempted; mSessionId = sessionId; } } @@ -105,11 +112,28 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } + FrameworkStatsLog.write( + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION, + mMediaProjectionSession.mSessionId, + mMediaProjectionSession.mUid, + mMediaProjectionSession.mIsExempted, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS + ); } @Override public void onStop(MediaProjectionInfo info) { if (DEBUG) Log.d(TAG, "onStop projection: " + info); + FrameworkStatsLog.write( + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION, + mMediaProjectionSession.mSessionId, + mMediaProjectionSession.mUid, + mMediaProjectionSession.mIsExempted, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP, + SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS + ); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "SensitiveContentProtectionManagerService.onProjectionStop"); try { @@ -207,28 +231,25 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } private void onProjectionStart(MediaProjectionInfo projectionInfo) { - // exempt on device screen recorder as well. - if ((mExemptedPackages != null && mExemptedPackages.contains( + int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0, + projectionInfo.getUserHandle().getIdentifier()); + boolean isPackageExempted = (mExemptedPackages != null && mExemptedPackages.contains( projectionInfo.getPackageName())) - || canRecordSensitiveContent(projectionInfo.getPackageName())) { - Log.w(TAG, projectionInfo.getPackageName() + " is exempted."); - return; - } + || canRecordSensitiveContent(projectionInfo.getPackageName()); // TODO(b/324447419): move GlobalSettings lookup to background thread - boolean disableScreenShareProtections = - Settings.Global.getInt(getContext().getContentResolver(), - DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0; - if (disableScreenShareProtections) { - Log.w(TAG, "Screen share protections disabled, ignoring projection start"); + boolean isFeatureDisabled = Settings.Global.getInt(getContext().getContentResolver(), + DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0; + mMediaProjectionSession = new MediaProjectionSession( + uid, isPackageExempted || isFeatureDisabled, new Random().nextLong()); + + if (isPackageExempted || isFeatureDisabled) { + Log.w(TAG, "projection session is exempted, package =" + + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled); return; } synchronized (mSensitiveContentProtectionLock) { mProjectionActive = true; - int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0, - projectionInfo.getUserHandle().getIdentifier()); - // TODO review sessionId, whether to use a sequence generator or random is good? - mMediaProjectionSession = new MediaProjectionSession(uid, new Random().nextLong()); if (sensitiveNotificationAppProtection()) { updateAppsThatShouldBlockScreenCapture(); } diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index c18bacb51671..72a55dbea481 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -177,6 +177,7 @@ public class Watchdog implements Dumpable { "android.hardware.biometrics.fingerprint.IFingerprint/", "android.hardware.bluetooth.IBluetoothHci/", "android.hardware.camera.provider.ICameraProvider/", + "android.hardware.drm.IDrmFactory/", "android.hardware.gnss.IGnss/", "android.hardware.graphics.allocator.IAllocator/", "android.hardware.graphics.composer3.IComposer/", diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c020a9ff1c27..c2613bc47eb2 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -993,6 +993,13 @@ class UserController implements Handler.Callback { if (isCurrentUserLU(userId)) { return USER_OP_IS_CURRENT; } + // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime... + final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID); + if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) { + if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) { + return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; + } + } TimingsTraceAndSlog t = new TimingsTraceAndSlog(); int[] usersToStop = getUsersToStopLU(userId); // If one of related users is system or current, no related users should be stopped diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index f1eea728bedd..951f676dd098 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -127,8 +127,7 @@ public class AudioDeviceBroker { private final Object mDeviceStateLock = new Object(); // Request to override default use of A2DP for media. - @GuardedBy("mDeviceStateLock") - private boolean mBluetoothA2dpEnabled; + private AtomicBoolean mBluetoothA2dpEnabled = new AtomicBoolean(false); // lock always taken when accessing AudioService.mSetModeDeathHandlers // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055 @@ -275,17 +274,8 @@ public class AudioDeviceBroker { } /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { - synchronized (mDeviceStateLock) { - if (mBluetoothA2dpEnabled == on) { - return; - } - mBluetoothA2dpEnabled = on; - mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); - sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - source); - } + mBluetoothA2dpEnabled.set(on); + sendLMsgNoDelay(MSG_L_SET_FORCE_BT_A2DP_USE, SENDMSG_REPLACE, source); } /** @@ -1223,16 +1213,8 @@ public class AudioDeviceBroker { } } - /*package*/ boolean isAvrcpAbsoluteVolumeSupported() { - synchronized (mDeviceStateLock) { - return mBtHelper.isAvrcpAbsoluteVolumeSupported(); - } - } - /*package*/ boolean isBluetoothA2dpOn() { - synchronized (mDeviceStateLock) { - return mBluetoothA2dpEnabled; - } + return mBluetoothA2dpEnabled.get(); } /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) { @@ -1601,15 +1583,12 @@ public class AudioDeviceBroker { .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).append(" src:").append(source).toString(); - synchronized (mDeviceStateLock) { - mBluetoothA2dpEnabled = on; - mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE); - onSetForceUse( - AudioSystem.FOR_MEDIA, - mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, - fromA2dp, - eventSource); - } + mBluetoothA2dpEnabled.set(on); + onSetForceUse( + AudioSystem.FOR_MEDIA, + on ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + fromA2dp, + eventSource); } /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, @@ -1658,9 +1637,7 @@ public class AudioDeviceBroker { } /*package*/ boolean getBluetoothA2dpEnabled() { - synchronized (mDeviceStateLock) { - return mBluetoothA2dpEnabled; - } + return mBluetoothA2dpEnabled.get(); } /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) { @@ -1821,9 +1798,12 @@ public class AudioDeviceBroker { } break; case MSG_IIL_SET_FORCE_USE: // intended fall-through - case MSG_IIL_SET_FORCE_BT_A2DP_USE: - onSetForceUse(msg.arg1, msg.arg2, - (msg.what == MSG_IIL_SET_FORCE_BT_A2DP_USE), (String) msg.obj); + onSetForceUse(msg.arg1, msg.arg2, false, (String) msg.obj); + break; + case MSG_L_SET_FORCE_BT_A2DP_USE: + int forcedUsage = mBluetoothA2dpEnabled.get() + ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; + onSetForceUse(AudioSystem.FOR_MEDIA, forcedUsage, true, (String) msg.obj); break; case MSG_REPORT_NEW_ROUTES: case MSG_REPORT_NEW_ROUTES_A2DP: @@ -1831,35 +1811,38 @@ public class AudioDeviceBroker { mDeviceInventory.onReportNewRoutes(); } break; - case MSG_L_SET_BT_ACTIVE_DEVICE: - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; - if (btInfo.mState == BluetoothProfile.STATE_CONNECTED - && !mBtHelper.isProfilePoxyConnected(btInfo.mProfile)) { - AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "msg: MSG_L_SET_BT_ACTIVE_DEVICE " - + "received with null profile proxy: " - + btInfo)).printLog(TAG)); - } else { - @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec = - mBtHelper.getCodecWithFallback(btInfo.mDevice, - btInfo.mProfile, btInfo.mIsLeOutput, - "MSG_L_SET_BT_ACTIVE_DEVICE"); + case MSG_L_SET_BT_ACTIVE_DEVICE: { + final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; + if (btInfo.mState == BluetoothProfile.STATE_CONNECTED + && !mBtHelper.isProfilePoxyConnected(btInfo.mProfile)) { + AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( + "msg: MSG_L_SET_BT_ACTIVE_DEVICE " + + "received with null profile proxy: " + + btInfo)).printLog(TAG)); + } else { + @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec = + mBtHelper.getCodecWithFallback(btInfo.mDevice, + btInfo.mProfile, btInfo.mIsLeOutput, + "MSG_L_SET_BT_ACTIVE_DEVICE"); + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { mDeviceInventory.onSetBtActiveDevice(btInfo, codec, (btInfo.mProfile - != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput) + != BluetoothProfile.LE_AUDIO + || btInfo.mIsLeOutput) ? mAudioService.getBluetoothContextualVolumeStream() : AudioSystem.STREAM_DEFAULT); if (btInfo.mProfile == BluetoothProfile.LE_AUDIO - || btInfo.mProfile == BluetoothProfile.HEARING_AID) { - onUpdateCommunicationRouteClient(isBluetoothScoRequested(), + || btInfo.mProfile + == BluetoothProfile.HEARING_AID) { + onUpdateCommunicationRouteClient( + isBluetoothScoRequested(), "setBluetoothActiveDevice"); } } } } - break; + } break; case MSG_BT_HEADSET_CNCT_FAILED: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { @@ -1883,11 +1866,11 @@ public class AudioDeviceBroker { break; case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: { final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; + @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec = + mBtHelper.getCodecWithFallback(btInfo.mDevice, + btInfo.mProfile, btInfo.mIsLeOutput, + "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE"); synchronized (mDeviceStateLock) { - @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec = - mBtHelper.getCodecWithFallback(btInfo.mDevice, - btInfo.mProfile, btInfo.mIsLeOutput, - "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE"); mDeviceInventory.onBluetoothDeviceConfigChange( btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE); } @@ -2098,7 +2081,7 @@ public class AudioDeviceBroker { private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2; private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3; private static final int MSG_IIL_SET_FORCE_USE = 4; - private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5; + private static final int MSG_L_SET_FORCE_BT_A2DP_USE = 5; private static final int MSG_TOGGLE_HDMI = 6; private static final int MSG_L_SET_BT_ACTIVE_DEVICE = 7; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; @@ -2295,7 +2278,7 @@ public class AudioDeviceBroker { MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE); MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE); MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT); - MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE); + MESSAGES_MUTE_MUSIC.add(MSG_L_SET_FORCE_BT_A2DP_USE); } private AtomicBoolean mMusicMuted = new AtomicBoolean(false); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 649b9efc3282..be47f8581db3 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -46,6 +46,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK import static com.android.media.audio.Flags.alarmMinVolumeZero; import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; import static com.android.media.audio.Flags.ringerModeAffectsAlarm; +import static com.android.media.audio.Flags.setStreamVolumeOrder; import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -4538,6 +4539,8 @@ public class AudioService extends IAudioService.Stub + focusFreezeTestApi()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); + pw.println("\tcom.android.media.audio.setStreamVolumeOrder:" + + setStreamVolumeOrder()); pw.println("\tandroid.media.audio.foregroundAudioControl:" + foregroundAudioControl()); } @@ -4705,6 +4708,30 @@ public class AudioService extends IAudioService.Stub index = rescaleIndex(index * 10, streamType, streamTypeAlias); + if (setStreamVolumeOrder()) { + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + + // volume is either 0 or max allowed for fixed volume devices + if (index != 0) { + index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); + if (index < 0) { + index = streamState.getMaxIndex(); + } + } + } + + if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, + flags)) { + onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, + // ada is non-null when called from setDeviceVolume, + // which shouldn't update the mute state + canChangeMuteAndUpdateController /*canChangeMute*/); + index = mStreamStates[streamType].getIndex(device); + } + } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { @@ -4738,26 +4765,28 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } - flags &= ~AudioManager.FLAG_FIXED_VOLUME; - if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { - flags |= AudioManager.FLAG_FIXED_VOLUME; + if (!setStreamVolumeOrder()) { + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; - // volume is either 0 or max allowed for fixed volume devices - if (index != 0) { - index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); - if (index < 0) { - index = streamState.getMaxIndex(); + // volume is either 0 or max allowed for fixed volume devices + if (index != 0) { + index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); + if (index < 0) { + index = streamState.getMaxIndex(); + } } } - } - if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, - flags)) { - onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, - // ada is non-null when called from setDeviceVolume, - // which shouldn't update the mute state - canChangeMuteAndUpdateController /*canChangeMute*/); - index = mStreamStates[streamType].getIndex(device); + if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, + flags)) { + onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, + // ada is non-null when called from setDeviceVolume, + // which shouldn't update the mute state + canChangeMuteAndUpdateController /*canChangeMute*/); + index = mStreamStates[streamType].getIndex(device); + } } synchronized (mHdmiClientLock) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 0f3f8073edcc..f3a5fdb3cacc 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -235,10 +235,6 @@ public class BtHelper { mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); } - /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() { - return (mA2dp != null && mAvrcpAbsVolSupported); - } - /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) { mAvrcpAbsVolSupported = supported; Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported); @@ -648,8 +644,6 @@ public class BtHelper { } } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") /*package*/ synchronized boolean isProfilePoxyConnected(int profile) { switch (profile) { case BluetoothProfile.HEADSET: diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index e2ae3def0b1b..e8394d43f266 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -399,7 +399,7 @@ public final class DeviceStateManagerService extends SystemService { final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE); final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE); - return new DeviceStateInfo(supportedStates, baseState, + return new DeviceStateInfo(new ArrayList<>(supportedStates), baseState, createMergedDeviceState(currentState, baseState)); } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 1546010717c5..4aab9d26dbcb 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -29,6 +29,9 @@ import android.app.TaskStackListener; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; @@ -37,19 +40,20 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.os.SystemClock; +import android.os.Trace; import android.util.EventLog; import android.util.MathUtils; import android.util.Slog; import android.util.SparseArray; +import android.util.TimeUtils; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; -import com.android.internal.os.Clock; import com.android.server.EventLogTags; import com.android.server.display.brightness.BrightnessEvent; -import com.android.server.display.brightness.LightSensorController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import java.io.PrintWriter; @@ -64,6 +68,8 @@ import java.lang.annotation.RetentionPolicy; public class AutomaticBrightnessController { private static final String TAG = "AutomaticBrightnessController"; + private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; + public static final int AUTO_BRIGHTNESS_ENABLED = 1; public static final int AUTO_BRIGHTNESS_DISABLED = 2; public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3; @@ -81,20 +87,32 @@ public class AutomaticBrightnessController { public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2; public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE; + // How long the current sensor reading is assumed to be valid beyond the current time. + // This provides a bit of prediction, as well as ensures that the weight for the last sample is + // non-zero, which in turn ensures that the total weight is non-zero. + private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; + // Debounce for sampling user-initiated changes in display brightness to ensure // the user is satisfied with the result before storing the sample. private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000; - private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 1; - private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 2; - private static final int MSG_UPDATE_FOREGROUND_APP = 3; - private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 4; - private static final int MSG_RUN_UPDATE = 5; - private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 6; + private static final int MSG_UPDATE_AMBIENT_LUX = 1; + private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; + private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3; + private static final int MSG_UPDATE_FOREGROUND_APP = 4; + private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; + private static final int MSG_RUN_UPDATE = 6; + private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7; // Callbacks for requesting updates to the display's power state private final Callbacks mCallbacks; + // The sensor manager. + private final SensorManager mSensorManager; + + // The light sensor, or null if not available or needed. + private final Sensor mLightSensor; + // The mapper to translate ambient lux to screen brightness in the range [0, 1.0]. @NonNull private BrightnessMappingStrategy mCurrentBrightnessMapper; @@ -109,21 +127,94 @@ public class AutomaticBrightnessController { // How much to scale doze brightness by (should be (0, 1.0]). private final float mDozeScaleFactor; + // Initial light sensor event rate in milliseconds. + private final int mInitialLightSensorRate; + + // Steady-state light sensor event rate in milliseconds. + private final int mNormalLightSensorRate; + + // The current light sensor event rate in milliseconds. + private int mCurrentLightSensorRate; + + // Stability requirements in milliseconds for accepting a new brightness level. This is used + // for debouncing the light sensor. Different constants are used to debounce the light sensor + // when adapting to brighter or darker environments. This parameter controls how quickly + // brightness changes occur in response to an observed change in light level that exceeds the + // hysteresis threshold. + private final long mBrighteningLightDebounceConfig; + private final long mDarkeningLightDebounceConfig; + private final long mBrighteningLightDebounceConfigIdle; + private final long mDarkeningLightDebounceConfigIdle; + + // If true immediately after the screen is turned on the controller will try to adjust the + // brightness based on the current sensor reads. If false, the controller will collect more data + // and only then decide whether to change brightness. + private final boolean mResetAmbientLuxAfterWarmUpConfig; + + // Period of time in which to consider light samples for a short/long-term estimate of ambient + // light in milliseconds. + private final int mAmbientLightHorizonLong; + private final int mAmbientLightHorizonShort; + + // The intercept used for the weighting calculation. This is used in order to keep all possible + // weighting values positive. + private final int mWeightingIntercept; + // Configuration object for determining thresholds to change brightness dynamically + private final HysteresisLevels mAmbientBrightnessThresholds; private final HysteresisLevels mScreenBrightnessThresholds; + private final HysteresisLevels mAmbientBrightnessThresholdsIdle; private final HysteresisLevels mScreenBrightnessThresholdsIdle; private boolean mLoggingEnabled; + + // Amount of time to delay auto-brightness after screen on while waiting for + // the light sensor to warm-up in milliseconds. + // May be 0 if no warm-up is required. + private int mLightSensorWarmUpTimeConfig; + + // Set to true if the light sensor is enabled. + private boolean mLightSensorEnabled; + + // The time when the light sensor was enabled. + private long mLightSensorEnableTime; + // The currently accepted nominal ambient light level. private float mAmbientLux; + + // The last calculated ambient light level (long time window). + private float mSlowAmbientLux; + + // The last calculated ambient light level (short time window). + private float mFastAmbientLux; + + // The last ambient lux value prior to passing the darkening or brightening threshold. + private float mPreThresholdLux; + // True if mAmbientLux holds a valid value. private boolean mAmbientLuxValid; + + // The ambient light level threshold at which to brighten or darken the screen. + private float mAmbientBrighteningThreshold; + private float mAmbientDarkeningThreshold; + // The last brightness value prior to passing the darkening or brightening threshold. private float mPreThresholdBrightness; // The screen brightness threshold at which to brighten or darken the screen. private float mScreenBrighteningThreshold; private float mScreenDarkeningThreshold; + // The most recent light sample. + private float mLastObservedLux = INVALID_LUX; + + // The time of the most light recent sample. + private long mLastObservedLuxTime; + + // The number of light samples collected since the light sensor was enabled. + private int mRecentLightSamples; + + // A ring buffer containing all of the recent ambient light sensor readings. + private AmbientLightRingBuffer mAmbientLightRingBuffer; // The handler private AutomaticBrightnessHandler mHandler; @@ -182,55 +273,88 @@ public class AutomaticBrightnessController { private Context mContext; private int mState = AUTO_BRIGHTNESS_DISABLED; - private final Clock mClock; + private Clock mClock; private final Injector mInjector; - private final LightSensorController mLightSensorController; - - AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager, + AutomaticBrightnessController(Callbacks callbacks, Looper looper, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessModeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - int displayId, LightSensorController.LightSensorControllerConfig config, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { - this(new Injector(), callbacks, looper, - brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor, - screenBrightnessThresholds, + this(new Injector(), callbacks, looper, sensorManager, lightSensor, + brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax, + dozeScaleFactor, lightSensorRate, initialLightSensorRate, + brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, + resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, + screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, - brightnessThrottler, userLux, userNits, - new LightSensorController(sensorManager, looper, displayId, config), - brightnessClamperController + brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux, + userNits, brightnessClamperController ); } @VisibleForTesting AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessRangeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - LightSensorController lightSensorController, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { mInjector = injector; mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; + mSensorManager = sensorManager; mCurrentBrightnessMapper = brightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT); mScreenBrightnessRangeMinimum = brightnessMin; mScreenBrightnessRangeMaximum = brightnessMax; + mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; mDozeScaleFactor = dozeScaleFactor; + mNormalLightSensorRate = lightSensorRate; + mInitialLightSensorRate = initialLightSensorRate; + mCurrentLightSensorRate = -1; + mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; + mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; + mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle; + mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle; + mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; + mAmbientLightHorizonLong = ambientLightHorizonLong; + mAmbientLightHorizonShort = ambientLightHorizonShort; + mWeightingIntercept = ambientLightHorizonLong; + mAmbientBrightnessThresholds = ambientBrightnessThresholds; + mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle; mScreenBrightnessThresholds = screenBrightnessThresholds; mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle; mShortTermModel = new ShortTermModel(); mPausedShortTermModel = new ShortTermModel(); mHandler = new AutomaticBrightnessHandler(looper); - mLightSensorController = lightSensorController; - mLightSensorController.setListener(this::setAmbientLux); + mAmbientLightRingBuffer = + new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock); + + if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { + mLightSensor = lightSensor; + } + mActivityTaskManager = ActivityTaskManager.getService(); mPackageManager = mContext.getPackageManager(); mTaskStackListener = new TaskStackListenerImpl(); @@ -282,13 +406,13 @@ public class AutomaticBrightnessController { if (brightnessEvent != null) { brightnessEvent.setLux( mAmbientLuxValid ? mAmbientLux : PowerManager.BRIGHTNESS_INVALID_FLOAT); + brightnessEvent.setPreThresholdLux(mPreThresholdLux); brightnessEvent.setPreThresholdBrightness(mPreThresholdBrightness); brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness); brightnessEvent.setFlags(brightnessEvent.getFlags() | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0) | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); - mLightSensorController.updateBrightnessEvent(brightnessEvent); } if (!mAmbientLuxValid) { @@ -311,22 +435,21 @@ public class AutomaticBrightnessController { */ public float getAutomaticScreenBrightnessBasedOnLastObservedLux( BrightnessEvent brightnessEvent) { - float lastObservedLux = mLightSensorController.getLastObservedLux(); - if (lastObservedLux == INVALID_LUX) { + if (mLastObservedLux == INVALID_LUX) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; } - float brightness = mCurrentBrightnessMapper.getBrightness(lastObservedLux, + float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux, mForegroundAppPackageName, mForegroundAppCategory); if (shouldApplyDozeScaleFactor()) { brightness *= mDozeScaleFactor; } if (brightnessEvent != null) { - brightnessEvent.setLux(lastObservedLux); + brightnessEvent.setLux(mLastObservedLux); brightnessEvent.setRecommendedBrightness(brightness); brightnessEvent.setFlags(brightnessEvent.getFlags() - | (lastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0) + | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0) | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); } @@ -378,7 +501,6 @@ public class AutomaticBrightnessController { public void stop() { setLightSensorEnabled(false); - mLightSensorController.stop(); } public boolean hasUserDataPoints() { @@ -407,6 +529,14 @@ public class AutomaticBrightnessController { return mAmbientLux; } + float getSlowAmbientLux() { + return mSlowAmbientLux; + } + + float getFastAmbientLux() { + return mFastAmbientLux; + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -485,13 +615,36 @@ public class AutomaticBrightnessController { pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mDozeScaleFactor=" + mDozeScaleFactor); + pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate); + pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate); + pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); + pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); + pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); + pw.println(" mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle); + pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle); + pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); + pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); + pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); + pw.println(" mWeightingIntercept=" + mWeightingIntercept); + pw.println(); pw.println("Automatic Brightness Controller State:"); + pw.println(" mLightSensor=" + mLightSensor); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); + pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate); pw.println(" mAmbientLux=" + mAmbientLux); pw.println(" mAmbientLuxValid=" + mAmbientLuxValid); + pw.println(" mPreThresholdLux=" + mPreThresholdLux); pw.println(" mPreThresholdBrightness=" + mPreThresholdBrightness); + pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold); + pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold); pw.println(" mScreenBrighteningThreshold=" + mScreenBrighteningThreshold); pw.println(" mScreenDarkeningThreshold=" + mScreenDarkeningThreshold); + pw.println(" mLastObservedLux=" + mLastObservedLux); + pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); + pw.println(" mRecentLightSamples=" + mRecentLightSamples); + pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); pw.println(" mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy)); pw.println(" mShortTermModel="); @@ -520,21 +673,22 @@ public class AutomaticBrightnessController { } pw.println(); + pw.println(" mAmbientBrightnessThresholds="); + mAmbientBrightnessThresholds.dump(pw); pw.println(" mScreenBrightnessThresholds="); mScreenBrightnessThresholds.dump(pw); pw.println(" mScreenBrightnessThresholdsIdle="); mScreenBrightnessThresholdsIdle.dump(pw); - - pw.println(); - mLightSensorController.dump(pw); + pw.println(" mAmbientBrightnessThresholdsIdle="); + mAmbientBrightnessThresholdsIdle.dump(pw); } public float[] getLastSensorValues() { - return mLightSensorController.getLastSensorValues(); + return mAmbientLightRingBuffer.getAllLuxValues(); } public long[] getLastSensorTimestamps() { - return mLightSensorController.getLastSensorTimestamps(); + return mAmbientLightRingBuffer.getAllTimestamps(); } private String configStateToString(int state) { @@ -551,33 +705,273 @@ public class AutomaticBrightnessController { } private boolean setLightSensorEnabled(boolean enable) { - if (enable && mLightSensorController.enableLightSensorIfNeeded()) { - registerForegroundAppUpdater(); - return true; - } else if (!enable && mLightSensorController.disableLightSensorIfNeeded()) { + if (enable) { + if (!mLightSensorEnabled) { + mLightSensorEnabled = true; + mLightSensorEnableTime = mClock.uptimeMillis(); + mCurrentLightSensorRate = mInitialLightSensorRate; + registerForegroundAppUpdater(); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + mCurrentLightSensorRate * 1000, mHandler); + return true; + } + } else if (mLightSensorEnabled) { + mLightSensorEnabled = false; + mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig; + if (!mAmbientLuxValid) { + mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT; + } mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mRawScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; - mAmbientLuxValid = mLightSensorController.hasValidAmbientLux(); + mRecentLightSamples = 0; + mAmbientLightRingBuffer.clear(); + mCurrentLightSensorRate = -1; + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); unregisterForegroundAppUpdater(); + mSensorManager.unregisterListener(mLightSensorListener); } return false; } + private void handleLightSensorEvent(long time, float lux) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux); + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); + + if (mAmbientLightRingBuffer.size() == 0) { + // switch to using the steady-state sample rate after grabbing the initial light sample + adjustLightSensorRate(mNormalLightSensorRate); + } + applyLightSensorMeasurement(time, lux); + updateAmbientLux(time); + } + + private void applyLightSensorMeasurement(long time, float lux) { + mRecentLightSamples++; + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); + mAmbientLightRingBuffer.push(time, lux); + + // Remember this sample value. + mLastObservedLux = lux; + mLastObservedLuxTime = time; + } + + private void adjustLightSensorRate(int lightSensorRate) { + // if the light sensor rate changed, update the sensor listener + if (lightSensorRate != mCurrentLightSensorRate) { + if (mLoggingEnabled) { + Slog.d(TAG, "adjustLightSensorRate: " + + "previousRate=" + mCurrentLightSensorRate + ", " + + "currentRate=" + lightSensorRate); + } + mCurrentLightSensorRate = lightSensorRate; + mSensorManager.unregisterListener(mLightSensorListener); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + lightSensorRate * 1000, mHandler); + } + } + private boolean setAutoBrightnessAdjustment(float adjustment) { return mCurrentBrightnessMapper.setAutoBrightnessAdjustment(adjustment); } private void setAmbientLux(float lux) { - // called by LightSensorController.setAmbientLux - mAmbientLuxValid = true; + if (mLoggingEnabled) { + Slog.d(TAG, "setAmbientLux(" + lux + ")"); + } + if (lux < 0) { + Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0"); + lux = 0; + } mAmbientLux = lux; + if (isInIdleMode()) { + mAmbientBrighteningThreshold = + mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux); + mAmbientDarkeningThreshold = + mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux); + } else { + mAmbientBrighteningThreshold = + mAmbientBrightnessThresholds.getBrighteningThreshold(lux); + mAmbientDarkeningThreshold = + mAmbientBrightnessThresholds.getDarkeningThreshold(lux); + } mBrightnessRangeController.onAmbientLuxChange(mAmbientLux); mBrightnessClamperController.onAmbientLuxChange(mAmbientLux); // If the short term model was invalidated and the change is drastic enough, reset it. mShortTermModel.maybeReset(mAmbientLux); - updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); + } + + private float calculateAmbientLux(long now, long horizon) { + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")"); + } + final int N = mAmbientLightRingBuffer.size(); + if (N == 0) { + Slog.e(TAG, "calculateAmbientLux: No ambient light readings available"); + return -1; + } + + // Find the first measurement that is just outside of the horizon. + int endIndex = 0; + final long horizonStartTime = now - horizon; + for (int i = 0; i < N-1; i++) { + if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) { + endIndex++; + } else { + break; + } + } + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" + + mAmbientLightRingBuffer.getTime(endIndex) + ", " + + mAmbientLightRingBuffer.getLux(endIndex) + ")"); + } + float sum = 0; + float totalWeight = 0; + long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS; + for (int i = N - 1; i >= endIndex; i--) { + long eventTime = mAmbientLightRingBuffer.getTime(i); + if (i == endIndex && eventTime < horizonStartTime) { + // If we're at the final value, make sure we only consider the part of the sample + // within our desired horizon. + eventTime = horizonStartTime; + } + final long startTime = eventTime - now; + float weight = calculateWeight(startTime, endTime); + float lux = mAmbientLightRingBuffer.getLux(i); + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " + + "lux=" + lux + ", " + + "weight=" + weight); + } + totalWeight += weight; + sum += lux * weight; + endTime = startTime; + } + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux: " + + "totalWeight=" + totalWeight + ", " + + "newAmbientLux=" + (sum / totalWeight)); + } + return sum / totalWeight; + } + + private float calculateWeight(long startDelta, long endDelta) { + return weightIntegral(endDelta) - weightIntegral(startDelta); + } + + // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the + // horizon we're looking at and provides a non-linear weighting for light samples. + private float weightIntegral(long x) { + return x * (x * 0.5f + mWeightingIntercept); + } + + private long nextAmbientLightBrighteningTransition(long time) { + final int N = mAmbientLightRingBuffer.size(); + long earliestValidTime = time; + for (int i = N - 1; i >= 0; i--) { + if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) { + break; + } + earliestValidTime = mAmbientLightRingBuffer.getTime(i); + } + return earliestValidTime + (isInIdleMode() + ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig); + } + + private long nextAmbientLightDarkeningTransition(long time) { + final int N = mAmbientLightRingBuffer.size(); + long earliestValidTime = time; + for (int i = N - 1; i >= 0; i--) { + if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) { + break; + } + earliestValidTime = mAmbientLightRingBuffer.getTime(i); + } + return earliestValidTime + (isInIdleMode() + ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig); + } + + private void updateAmbientLux() { + long time = mClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); + updateAmbientLux(time); + } + + private void updateAmbientLux(long time) { + // If the light sensor was just turned on then immediately update our initial + // estimate of the current ambient light level. + if (!mAmbientLuxValid) { + final long timeWhenSensorWarmedUp = + mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; + if (time < timeWhenSensorWarmedUp) { + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + + "time=" + time + ", " + + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); + } + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, + timeWhenSensorWarmedUp); + return; + } + setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort)); + mAmbientLuxValid = true; + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: Initializing: " + + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + + "mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); + } + + long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term + // change in lighting conditions, and a fast ambient lux to determine what the new + // brightness situation is since the slow lux can be quite slow to converge. + // + // Note that both values need to be checked for sufficient change before updating the + // proposed ambient light value since the slow value might be sufficiently far enough away + // from the fast value to cause a recalculation while its actually just converging on + // the fast value still. + mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); + mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); + + if ((mSlowAmbientLux >= mAmbientBrighteningThreshold + && mFastAmbientLux >= mAmbientBrighteningThreshold + && nextBrightenTransition <= time) + || (mSlowAmbientLux <= mAmbientDarkeningThreshold + && mFastAmbientLux <= mAmbientDarkeningThreshold + && nextDarkenTransition <= time)) { + mPreThresholdLux = mAmbientLux; + setAmbientLux(mFastAmbientLux); + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: " + + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " + + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", " + + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + + "mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); + nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + } + long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); + // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't + // exceed the necessary threshold, then it's possible we'll get a transition time prior to + // now. Rather than continually checking to see whether the weighted lux exceeds the + // threshold, schedule an update for when we'd normally expect another light sample, which + // should be enough time to decide whether we should actually transition to the new + // weighted ambient lux or not. + nextTransitionTime = + nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate; + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); + } + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime); } private void updateAutoBrightness(boolean sendUpdate, boolean isManuallySet) { @@ -678,7 +1072,8 @@ public class AutomaticBrightnessController { if (mLoggingEnabled) { Slog.d(TAG, "Auto-brightness adjustment changed by user: " + "lux=" + mAmbientLux + ", " - + "brightness=" + mScreenAutoBrightness); + + "brightness=" + mScreenAutoBrightness + ", " + + "ring=" + mAmbientLightRingBuffer); } EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ, @@ -808,7 +1203,6 @@ public class AutomaticBrightnessController { if (mode == AUTO_BRIGHTNESS_MODE_IDLE || mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) { switchModeAndShortTermModels(mode); - mLightSensorController.setIdleMode(isInIdleMode()); } else { resetShortTermModel(); mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode); @@ -965,6 +1359,10 @@ public class AutomaticBrightnessController { updateAutoBrightness(true /*sendUpdate*/, false /*isManuallySet*/); break; + case MSG_UPDATE_AMBIENT_LUX: + updateAmbientLux(); + break; + case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE: collectBrightnessAdjustmentSample(); break; @@ -988,6 +1386,22 @@ public class AutomaticBrightnessController { } } + private final SensorEventListener mLightSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mLightSensorEnabled) { + final long time = mClock.uptimeMillis(); + final float lux = event.values[0]; + handleLightSensorEvent(time, lux); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + // Call back whenever the tasks stack changes, which includes tasks being created, removed, and // moving to top. class TaskStackListenerImpl extends TaskStackListener { @@ -1002,13 +1416,192 @@ public class AutomaticBrightnessController { void updateBrightness(); } + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + + /** + * A ring buffer of ambient light measurements sorted by time. + * + * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted + * from oldest to newest. + */ + private static final class AmbientLightRingBuffer { + // Proportional extra capacity of the buffer beyond the expected number of light samples + // in the horizon + private static final float BUFFER_SLACK = 1.5f; + private float[] mRingLux; + private long[] mRingTime; + private int mCapacity; + + // The first valid element and the next open slot. + // Note that if mCount is zero then there are no valid elements. + private int mStart; + private int mEnd; + private int mCount; + Clock mClock; + + public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon, Clock clock) { + if (lightSensorRate <= 0) { + throw new IllegalArgumentException("lightSensorRate must be above 0"); + } + mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate); + mRingLux = new float[mCapacity]; + mRingTime = new long[mCapacity]; + mClock = clock; + } + + public float getLux(int index) { + return mRingLux[offsetOf(index)]; + } + + public float[] getAllLuxValues() { + float[] values = new float[mCount]; + if (mCount == 0) { + return values; + } + + if (mStart < mEnd) { + System.arraycopy(mRingLux, mStart, values, 0, mCount); + } else { + System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart); + System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd); + } + + return values; + } + + public long getTime(int index) { + return mRingTime[offsetOf(index)]; + } + + public long[] getAllTimestamps() { + long[] values = new long[mCount]; + if (mCount == 0) { + return values; + } + + if (mStart < mEnd) { + System.arraycopy(mRingTime, mStart, values, 0, mCount); + } else { + System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart); + System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd); + } + + return values; + } + + public void push(long time, float lux) { + int next = mEnd; + if (mCount == mCapacity) { + int newSize = mCapacity * 2; + + float[] newRingLux = new float[newSize]; + long[] newRingTime = new long[newSize]; + int length = mCapacity - mStart; + System.arraycopy(mRingLux, mStart, newRingLux, 0, length); + System.arraycopy(mRingTime, mStart, newRingTime, 0, length); + if (mStart != 0) { + System.arraycopy(mRingLux, 0, newRingLux, length, mStart); + System.arraycopy(mRingTime, 0, newRingTime, length, mStart); + } + mRingLux = newRingLux; + mRingTime = newRingTime; + + next = mCapacity; + mCapacity = newSize; + mStart = 0; + } + mRingTime[next] = time; + mRingLux[next] = lux; + mEnd = next + 1; + if (mEnd == mCapacity) { + mEnd = 0; + } + mCount++; + } + + public void prune(long horizon) { + if (mCount == 0) { + return; + } + + while (mCount > 1) { + int next = mStart + 1; + if (next >= mCapacity) { + next -= mCapacity; + } + if (mRingTime[next] > horizon) { + // Some light sensors only produce data upon a change in the ambient light + // levels, so we need to consider the previous measurement as the ambient light + // level for all points in time up until we receive a new measurement. Thus, we + // always want to keep the youngest element that would be removed from the + // buffer and just set its measurement time to the horizon time since at that + // point it is the ambient light level, and to remove it would be to drop a + // valid data point within our horizon. + break; + } + mStart = next; + mCount -= 1; + } + + if (mRingTime[mStart] < horizon) { + mRingTime[mStart] = horizon; + } + } + + public int size() { + return mCount; + } + + public void clear() { + mStart = 0; + mEnd = 0; + mCount = 0; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append('['); + for (int i = 0; i < mCount; i++) { + final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); + if (i != 0) { + buf.append(", "); + } + buf.append(getLux(i)); + buf.append(" / "); + buf.append(next - getTime(i)); + buf.append("ms"); + } + buf.append(']'); + return buf.toString(); + } + + private int offsetOf(int index) { + if (index >= mCount || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + index += mStart; + if (index >= mCapacity) { + index -= mCapacity; + } + return index; + } + } + public static class Injector { public Handler getBackgroundThreadHandler() { return BackgroundThread.getHandler(); } Clock createClock() { - return Clock.SYSTEM_CLOCK; + return SystemClock::uptimeMillis; } } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 641b6a2f170b..61ecb934dd38 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1930,7 +1930,6 @@ public class DisplayDeviceConfig { * * @return true if even dimmer mode is enabled */ - @VisibleForTesting public boolean isEvenDimmerAvailable() { return mEvenDimmerBrightnessData != null; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 043ff0e9e394..b21a89a92803 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -82,7 +82,6 @@ import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.DisplayBrightnessController; -import com.android.server.display.brightness.LightSensorController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; @@ -1050,13 +1049,102 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (defaultModeBrightnessMapper != null) { + // Ambient Lux - Active Mode Brightness Thresholds + float[] ambientBrighteningThresholds = + mDisplayDeviceConfig.getAmbientBrighteningPercentages(); + float[] ambientDarkeningThresholds = + mDisplayDeviceConfig.getAmbientDarkeningPercentages(); + float[] ambientBrighteningLevels = + mDisplayDeviceConfig.getAmbientBrighteningLevels(); + float[] ambientDarkeningLevels = + mDisplayDeviceConfig.getAmbientDarkeningLevels(); + float ambientDarkeningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(); + float ambientBrighteningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(); + HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold, + ambientBrighteningMinThreshold); + // Display - Active Mode Brightness Thresholds - HysteresisLevels screenBrightnessThresholds = - mInjector.getBrightnessThresholdsHysteresisLevels(mDisplayDeviceConfig); + float[] screenBrighteningThresholds = + mDisplayDeviceConfig.getScreenBrighteningPercentages(); + float[] screenDarkeningThresholds = + mDisplayDeviceConfig.getScreenDarkeningPercentages(); + float[] screenBrighteningLevels = + mDisplayDeviceConfig.getScreenBrighteningLevels(); + float[] screenDarkeningLevels = + mDisplayDeviceConfig.getScreenDarkeningLevels(); + float screenDarkeningMinThreshold = + mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); + float screenBrighteningMinThreshold = + mDisplayDeviceConfig.getScreenBrighteningMinThreshold(); + HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, + screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold, + screenBrighteningMinThreshold, true); + + // Ambient Lux - Idle Screen Brightness Thresholds + float ambientDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(); + float ambientBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(); + float[] ambientBrighteningThresholdsIdle = + mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(); + float[] ambientDarkeningThresholdsIdle = + mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(); + float[] ambientBrighteningLevelsIdle = + mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(); + float[] ambientDarkeningLevelsIdle = + mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(); + HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels( + ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle, + ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle, + ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle); // Display - Idle Screen Brightness Thresholds - HysteresisLevels screenBrightnessThresholdsIdle = - mInjector.getBrightnessThresholdsIdleHysteresisLevels(mDisplayDeviceConfig); + float screenDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(); + float screenBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(); + float[] screenBrighteningThresholdsIdle = + mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(); + float[] screenDarkeningThresholdsIdle = + mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(); + float[] screenBrighteningLevelsIdle = + mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(); + float[] screenDarkeningLevelsIdle = + mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(); + HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels( + screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle, + screenBrighteningLevelsIdle, screenDarkeningLevelsIdle, + screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle); + + long brighteningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounce(); + long darkeningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounce(); + long brighteningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounceIdle(); + long darkeningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounceIdle(); + boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean( + R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); + + int lightSensorWarmUpTimeConfig = context.getResources().getInteger( + R.integer.config_lightSensorWarmupTime); + int lightSensorRate = context.getResources().getInteger( + R.integer.config_autoBrightnessLightSensorRate); + int initialLightSensorRate = context.getResources().getInteger( + R.integer.config_autoBrightnessInitialLightSensorRate); + if (initialLightSensorRate == -1) { + initialLightSensorRate = lightSensorRate; + } else if (initialLightSensorRate > lightSensorRate) { + Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate (" + + initialLightSensorRate + ") to be less than or equal to " + + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ")."); + } loadAmbientLightSensor(); // BrightnessTracker should only use one light sensor, we want to use the light sensor @@ -1068,15 +1156,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); } - - LightSensorController.LightSensorControllerConfig config = - mInjector.getLightSensorControllerConfig(context, mDisplayDeviceConfig); mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController( - this, handler.getLooper(), mSensorManager, brightnessMappers, - PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, - screenBrightnessThresholds, screenBrightnessThresholdsIdle, - mContext, mBrightnessRangeController, - mBrightnessThrottler, userLux, userNits, mDisplayId, config, + this, handler.getLooper(), mSensorManager, mLightSensor, + brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate, + initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, + brighteningLightDebounceIdle, darkeningLightDebounceIdle, + autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, + screenBrightnessThresholds, ambientBrightnessThresholdsIdle, + screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, + mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits, mBrightnessClamperController); mDisplayBrightnessController.setAutomaticBrightnessController( mAutomaticBrightnessController); @@ -3083,34 +3173,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call AutomaticBrightnessController getAutomaticBrightnessController( AutomaticBrightnessController.Callbacks callbacks, Looper looper, - SensorManager sensorManager, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, + HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessModeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - int displayId, LightSensorController.LightSensorControllerConfig config, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { - return new AutomaticBrightnessController(callbacks, looper, sensorManager, - brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor, - screenBrightnessThresholds, screenBrightnessThresholdsIdle, context, - brightnessModeController, brightnessThrottler, userLux, userNits, displayId, - config, brightnessClamperController); - } - - LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig( - Context context, DisplayDeviceConfig displayDeviceConfig) { - return LightSensorController.LightSensorControllerConfig.create( - context.getResources(), displayDeviceConfig); - } - HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) { - return HysteresisLevels.getBrightnessThresholdsIdle(ddc); - } - - HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) { - return HysteresisLevels.getBrightnessThresholds(ddc); + return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor, + brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, + brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, + brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, + resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, + screenBrightnessThresholds, ambientBrightnessThresholdsIdle, + screenBrightnessThresholdsIdle, context, brightnessModeController, + brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux, + userNits, brightnessClamperController); } BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context, @@ -3120,6 +3208,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController); } + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold) { + return new HysteresisLevels(brighteningThresholdsPercentages, + darkeningThresholdsPercentages, brighteningThresholdLevels, + darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold); + } + + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold, boolean potentialOldBrightnessRange) { + return new HysteresisLevels(brighteningThresholdsPercentages, + darkeningThresholdsPercentages, brighteningThresholdLevels, + darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold, + potentialOldBrightnessRange); + } + ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController( SensorManager sensorManager, Sensor lightSensor, diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java index bb349e76857c..0521b8ac4f3b 100644 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ b/services/core/java/com/android/server/display/HysteresisLevels.java @@ -18,7 +18,6 @@ package com.android.server.display; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; @@ -53,8 +52,7 @@ public class HysteresisLevels { * @param potentialOldBrightnessRange whether or not the values used could be from the old * screen brightness range ie, between 1-255. */ - @VisibleForTesting - public HysteresisLevels(float[] brighteningThresholdsPercentages, + HysteresisLevels(float[] brighteningThresholdsPercentages, float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold, @@ -140,10 +138,7 @@ public class HysteresisLevels { return levelArray; } - /** - * Print the object's debug information into the given stream. - */ - public void dump(PrintWriter pw) { + void dump(PrintWriter pw) { pw.println("HysteresisLevels"); pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels)); pw.println(" mBrighteningThresholdsPercentages=" @@ -154,45 +149,4 @@ public class HysteresisLevels { + Arrays.toString(mDarkeningThresholdsPercentages)); pw.println(" mMinDarkening=" + mMinDarkening); } - - - /** - * Creates hysteresis levels for Active Ambient Lux - */ - public static HysteresisLevels getAmbientBrightnessThresholds(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getAmbientBrighteningPercentages(), - ddc.getAmbientDarkeningPercentages(), ddc.getAmbientBrighteningLevels(), - ddc.getAmbientDarkeningLevels(), ddc.getAmbientLuxDarkeningMinThreshold(), - ddc.getAmbientLuxBrighteningMinThreshold()); - } - - /** - * Creates hysteresis levels for Active Screen Brightness - */ - public static HysteresisLevels getBrightnessThresholds(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getScreenBrighteningPercentages(), - ddc.getScreenDarkeningPercentages(), ddc.getScreenBrighteningLevels(), - ddc.getScreenDarkeningLevels(), ddc.getScreenDarkeningMinThreshold(), - ddc.getScreenBrighteningMinThreshold(), true); - } - - /** - * Creates hysteresis levels for Idle Ambient Lux - */ - public static HysteresisLevels getAmbientBrightnessThresholdsIdle(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getAmbientBrighteningPercentagesIdle(), - ddc.getAmbientDarkeningPercentagesIdle(), ddc.getAmbientBrighteningLevelsIdle(), - ddc.getAmbientDarkeningLevelsIdle(), ddc.getAmbientLuxDarkeningMinThresholdIdle(), - ddc.getAmbientLuxBrighteningMinThresholdIdle()); - } - - /** - * Creates hysteresis levels for Idle Screen Brightness - */ - public static HysteresisLevels getBrightnessThresholdsIdle(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getScreenBrighteningPercentagesIdle(), - ddc.getScreenDarkeningPercentagesIdle(), ddc.getScreenBrighteningLevelsIdle(), - ddc.getScreenDarkeningLevelsIdle(), ddc.getScreenDarkeningMinThresholdIdle(), - ddc.getScreenBrighteningMinThresholdIdle()); - } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index a577e225076f..1dfe03735595 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -940,7 +940,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { final float nits = backlightToNits(backlight); final float sdrNits = backlightToNits(sdrBacklight); - if (getFeatureFlags().isEvenDimmerEnabled()) { + if (getFeatureFlags().isEvenDimmerEnabled() + && mDisplayDeviceConfig != null + && mDisplayDeviceConfig.isEvenDimmerAvailable()) { applyColorMatrixBasedDimming(brightnessState); } diff --git a/services/core/java/com/android/server/display/brightness/LightSensorController.java b/services/core/java/com/android/server/display/brightness/LightSensorController.java deleted file mode 100644 index d82d6983c3ba..000000000000 --- a/services/core/java/com/android/server/display/brightness/LightSensorController.java +++ /dev/null @@ -1,868 +0,0 @@ -/* - * 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.display.brightness; - -import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX; - -import android.annotation.Nullable; -import android.content.res.Resources; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Handler; -import android.os.Looper; -import android.os.PowerManager; -import android.os.Trace; -import android.util.Slog; -import android.util.TimeUtils; -import android.view.Display; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.Clock; -import com.android.server.display.DisplayDeviceConfig; -import com.android.server.display.HysteresisLevels; -import com.android.server.display.config.SensorData; -import com.android.server.display.utils.SensorUtils; - -import java.io.PrintWriter; - -/** - * Manages light sensor subscription and notifies its listeners about ambient lux changes based on - * configuration - */ -public class LightSensorController { - // How long the current sensor reading is assumed to be valid beyond the current time. - // This provides a bit of prediction, as well as ensures that the weight for the last sample is - // non-zero, which in turn ensures that the total weight is non-zero. - private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; - - // Proportional extra capacity of the buffer beyond the expected number of light samples - // in the horizon - private static final float BUFFER_SLACK = 1.5f; - - private boolean mLoggingEnabled; - private boolean mLightSensorEnabled; - private long mLightSensorEnableTime; - // The current light sensor event rate in milliseconds. - private int mCurrentLightSensorRate = -1; - // The number of light samples collected since the light sensor was enabled. - private int mRecentLightSamples; - private float mAmbientLux; - // True if mAmbientLux holds a valid value. - private boolean mAmbientLuxValid; - // The last ambient lux value prior to passing the darkening or brightening threshold. - private float mPreThresholdLux; - // The most recent light sample. - private float mLastObservedLux = INVALID_LUX; - // The time of the most light recent sample. - private long mLastObservedLuxTime; - // The last calculated ambient light level (long time window). - private float mSlowAmbientLux; - // The last calculated ambient light level (short time window). - private float mFastAmbientLux; - private volatile boolean mIsIdleMode; - // The ambient light level threshold at which to brighten or darken the screen. - private float mAmbientBrighteningThreshold; - private float mAmbientDarkeningThreshold; - - private final LightSensorControllerConfig mConfig; - - // The light sensor, or null if not available or needed. - @Nullable - private final Sensor mLightSensor; - - // A ring buffer containing all of the recent ambient light sensor readings. - private final AmbientLightRingBuffer mAmbientLightRingBuffer; - - private final Injector mInjector; - - private final SensorEventListener mLightSensorListener = new SensorEventListener() { - @Override - public void onSensorChanged(SensorEvent event) { - if (mLightSensorEnabled) { - final long time = mClock.uptimeMillis(); - final float lux = event.values[0]; - handleLightSensorEvent(time, lux); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // Not used. - } - }; - - // Runnable used to delay ambient lux update when: - // 1) update triggered before configured warm up time - // 2) next brightening or darkening transition need to happen - private final Runnable mAmbientLuxUpdater = this::updateAmbientLux; - - private final Clock mClock; - - private final Handler mHandler; - - private final String mTag; - - private LightSensorListener mListener; - - public LightSensorController( - SensorManager sensorManager, - Looper looper, - int displayId, - LightSensorControllerConfig config) { - this(config, new RealInjector(sensorManager, displayId), new LightSensorHandler(looper)); - } - - @VisibleForTesting - LightSensorController( - LightSensorControllerConfig config, - Injector injector, - Handler handler) { - if (config.mNormalLightSensorRate <= 0) { - throw new IllegalArgumentException("lightSensorRate must be above 0"); - } - mInjector = injector; - int bufferInitialCapacity = (int) Math.ceil( - config.mAmbientLightHorizonLong * BUFFER_SLACK / config.mNormalLightSensorRate); - mClock = injector.getClock(); - mHandler = handler; - mAmbientLightRingBuffer = new AmbientLightRingBuffer(bufferInitialCapacity, mClock); - mConfig = config; - mLightSensor = mInjector.getLightSensor(mConfig); - mTag = mInjector.getTag(); - } - - public void setListener(LightSensorListener listener) { - mListener = listener; - } - - /** - * @return true if sensor registered, false if sensor already registered - */ - public boolean enableLightSensorIfNeeded() { - if (!mLightSensorEnabled) { - mLightSensorEnabled = true; - mLightSensorEnableTime = mClock.uptimeMillis(); - mCurrentLightSensorRate = mConfig.mInitialLightSensorRate; - mInjector.registerLightSensorListener( - mLightSensorListener, mLightSensor, mCurrentLightSensorRate, mHandler); - return true; - } - return false; - } - - /** - * @return true if sensor unregistered, false if sensor already unregistered - */ - public boolean disableLightSensorIfNeeded() { - if (mLightSensorEnabled) { - mLightSensorEnabled = false; - mAmbientLuxValid = !mConfig.mResetAmbientLuxAfterWarmUpConfig; - if (!mAmbientLuxValid) { - mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT; - } - mRecentLightSamples = 0; - mAmbientLightRingBuffer.clear(); - mCurrentLightSensorRate = -1; - mInjector.unregisterLightSensorListener(mLightSensorListener); - return true; - } - return false; - } - - public void setLoggingEnabled(boolean loggingEnabled) { - mLoggingEnabled = loggingEnabled; - } - - /** - * Updates BrightnessEvent with LightSensorController details - */ - public void updateBrightnessEvent(BrightnessEvent brightnessEvent) { - brightnessEvent.setPreThresholdLux(mPreThresholdLux); - } - - /** - * Print the object's debug information into the given stream. - */ - public void dump(PrintWriter pw) { - pw.println("LightSensorController state:"); - pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); - pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); - pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate); - pw.println(" mRecentLightSamples=" + mRecentLightSamples); - pw.println(" mAmbientLux=" + mAmbientLux); - pw.println(" mAmbientLuxValid=" + mAmbientLuxValid); - pw.println(" mPreThresholdLux=" + mPreThresholdLux); - pw.println(" mLastObservedLux=" + mLastObservedLux); - pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); - pw.println(" mSlowAmbientLux=" + mSlowAmbientLux); - pw.println(" mFastAmbientLux=" + mFastAmbientLux); - pw.println(" mIsIdleMode=" + mIsIdleMode); - pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold); - pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold); - pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); - pw.println(" mLightSensor=" + mLightSensor); - mConfig.dump(pw); - } - - /** - * This method should be called when this LightSensorController is no longer in use - * i.e. when corresponding display removed - */ - public void stop() { - mHandler.removeCallbacksAndMessages(null); - disableLightSensorIfNeeded(); - } - - public void setIdleMode(boolean isIdleMode) { - mIsIdleMode = isIdleMode; - } - - /** - * returns true if LightSensorController holds valid ambient lux value - */ - public boolean hasValidAmbientLux() { - return mAmbientLuxValid; - } - - /** - * returns all last observed sensor values - */ - public float[] getLastSensorValues() { - return mAmbientLightRingBuffer.getAllLuxValues(); - } - - /** - * returns all last observed sensor event timestamps - */ - public long[] getLastSensorTimestamps() { - return mAmbientLightRingBuffer.getAllTimestamps(); - } - - public float getLastObservedLux() { - return mLastObservedLux; - } - - private void handleLightSensorEvent(long time, float lux) { - Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux); - mHandler.removeCallbacks(mAmbientLuxUpdater); - - if (mAmbientLightRingBuffer.size() == 0) { - // switch to using the steady-state sample rate after grabbing the initial light sample - adjustLightSensorRate(mConfig.mNormalLightSensorRate); - } - applyLightSensorMeasurement(time, lux); - updateAmbientLux(time); - } - - private void applyLightSensorMeasurement(long time, float lux) { - mRecentLightSamples++; - mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong); - mAmbientLightRingBuffer.push(time, lux); - // Remember this sample value. - mLastObservedLux = lux; - mLastObservedLuxTime = time; - } - - private void adjustLightSensorRate(int lightSensorRate) { - // if the light sensor rate changed, update the sensor listener - if (lightSensorRate != mCurrentLightSensorRate) { - if (mLoggingEnabled) { - Slog.d(mTag, "adjustLightSensorRate: " - + "previousRate=" + mCurrentLightSensorRate + ", " - + "currentRate=" + lightSensorRate); - } - mCurrentLightSensorRate = lightSensorRate; - mInjector.unregisterLightSensorListener(mLightSensorListener); - mInjector.registerLightSensorListener( - mLightSensorListener, mLightSensor, lightSensorRate, mHandler); - } - } - - private void setAmbientLux(float lux) { - if (mLoggingEnabled) { - Slog.d(mTag, "setAmbientLux(" + lux + ")"); - } - if (lux < 0) { - Slog.w(mTag, "Ambient lux was negative, ignoring and setting to 0"); - lux = 0; - } - mAmbientLux = lux; - - if (mIsIdleMode) { - mAmbientBrighteningThreshold = - mConfig.mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux); - mAmbientDarkeningThreshold = - mConfig.mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux); - } else { - mAmbientBrighteningThreshold = - mConfig.mAmbientBrightnessThresholds.getBrighteningThreshold(lux); - mAmbientDarkeningThreshold = - mConfig.mAmbientBrightnessThresholds.getDarkeningThreshold(lux); - } - - mListener.onAmbientLuxChange(mAmbientLux); - } - - private float calculateAmbientLux(long now, long horizon) { - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux(" + now + ", " + horizon + ")"); - } - final int size = mAmbientLightRingBuffer.size(); - if (size == 0) { - Slog.e(mTag, "calculateAmbientLux: No ambient light readings available"); - return -1; - } - - // Find the first measurement that is just outside of the horizon. - int endIndex = 0; - final long horizonStartTime = now - horizon; - for (int i = 0; i < size - 1; i++) { - if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) { - endIndex++; - } else { - break; - } - } - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" - + mAmbientLightRingBuffer.getTime(endIndex) + ", " - + mAmbientLightRingBuffer.getLux(endIndex) + ")"); - } - float sum = 0; - float totalWeight = 0; - long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS; - for (int i = size - 1; i >= endIndex; i--) { - long eventTime = mAmbientLightRingBuffer.getTime(i); - if (i == endIndex && eventTime < horizonStartTime) { - // If we're at the final value, make sure we only consider the part of the sample - // within our desired horizon. - eventTime = horizonStartTime; - } - final long startTime = eventTime - now; - float weight = calculateWeight(startTime, endTime); - float lux = mAmbientLightRingBuffer.getLux(i); - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " - + "lux=" + lux + ", " - + "weight=" + weight); - } - totalWeight += weight; - sum += lux * weight; - endTime = startTime; - } - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux: " - + "totalWeight=" + totalWeight + ", " - + "newAmbientLux=" + (sum / totalWeight)); - } - return sum / totalWeight; - } - - private float calculateWeight(long startDelta, long endDelta) { - return weightIntegral(endDelta) - weightIntegral(startDelta); - } - - // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the - // horizon we're looking at and provides a non-linear weighting for light samples. - private float weightIntegral(long x) { - return x * (x * 0.5f + mConfig.mWeightingIntercept); - } - - private long nextAmbientLightBrighteningTransition(long time) { - final int size = mAmbientLightRingBuffer.size(); - long earliestValidTime = time; - for (int i = size - 1; i >= 0; i--) { - if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) { - break; - } - earliestValidTime = mAmbientLightRingBuffer.getTime(i); - } - return earliestValidTime + (mIsIdleMode ? mConfig.mBrighteningLightDebounceConfigIdle - : mConfig.mBrighteningLightDebounceConfig); - } - - private long nextAmbientLightDarkeningTransition(long time) { - final int size = mAmbientLightRingBuffer.size(); - long earliestValidTime = time; - for (int i = size - 1; i >= 0; i--) { - if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) { - break; - } - earliestValidTime = mAmbientLightRingBuffer.getTime(i); - } - return earliestValidTime + (mIsIdleMode ? mConfig.mDarkeningLightDebounceConfigIdle - : mConfig.mDarkeningLightDebounceConfig); - } - - private void updateAmbientLux() { - long time = mClock.uptimeMillis(); - mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong); - updateAmbientLux(time); - } - - private void updateAmbientLux(long time) { - // If the light sensor was just turned on then immediately update our initial - // estimate of the current ambient light level. - if (!mAmbientLuxValid) { - final long timeWhenSensorWarmedUp = - mConfig.mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; - if (time < timeWhenSensorWarmedUp) { - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: Sensor not ready yet: " - + "time=" + time + ", " - + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); - } - mHandler.postAtTime(mAmbientLuxUpdater, timeWhenSensorWarmedUp); - return; - } - mAmbientLuxValid = true; - setAmbientLux(calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort)); - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: Initializing: " - + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " - + "mAmbientLux=" + mAmbientLux); - } - } - - long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); - long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); - // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term - // change in lighting conditions, and a fast ambient lux to determine what the new - // brightness situation is since the slow lux can be quite slow to converge. - // - // Note that both values need to be checked for sufficient change before updating the - // proposed ambient light value since the slow value might be sufficiently far enough away - // from the fast value to cause a recalculation while its actually just converging on - // the fast value still. - mSlowAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonLong); - mFastAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort); - - if ((mSlowAmbientLux >= mAmbientBrighteningThreshold - && mFastAmbientLux >= mAmbientBrighteningThreshold - && nextBrightenTransition <= time) - || (mSlowAmbientLux <= mAmbientDarkeningThreshold - && mFastAmbientLux <= mAmbientDarkeningThreshold - && nextDarkenTransition <= time)) { - mPreThresholdLux = mAmbientLux; - setAmbientLux(mFastAmbientLux); - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: " - + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " - + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " - + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", " - + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " - + "mAmbientLux=" + mAmbientLux); - } - nextBrightenTransition = nextAmbientLightBrighteningTransition(time); - nextDarkenTransition = nextAmbientLightDarkeningTransition(time); - } - long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); - // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't - // exceed the necessary threshold, then it's possible we'll get a transition time prior to - // now. Rather than continually checking to see whether the weighted lux exceeds the - // threshold, schedule an update for when we'd normally expect another light sample, which - // should be enough time to decide whether we should actually transition to the new - // weighted ambient lux or not. - nextTransitionTime = nextTransitionTime > time ? nextTransitionTime - : time + mConfig.mNormalLightSensorRate; - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: Scheduling ambient lux update for " - + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); - } - mHandler.postAtTime(mAmbientLuxUpdater, nextTransitionTime); - } - - public interface LightSensorListener { - /** - * Called when new ambient lux value is ready - */ - void onAmbientLuxChange(float ambientLux); - } - - private static final class LightSensorHandler extends Handler { - private LightSensorHandler(Looper looper) { - super(looper, /* callback= */ null, /* async= */ true); - } - } - - /** - * A ring buffer of ambient light measurements sorted by time. - * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted - * from oldest to newest. - */ - @VisibleForTesting - static final class AmbientLightRingBuffer { - - private float[] mRingLux; - private long[] mRingTime; - private int mCapacity; - - // The first valid element and the next open slot. - // Note that if mCount is zero then there are no valid elements. - private int mStart; - private int mEnd; - private int mCount; - - private final Clock mClock; - - @VisibleForTesting - AmbientLightRingBuffer(int initialCapacity, Clock clock) { - mCapacity = initialCapacity; - mRingLux = new float[mCapacity]; - mRingTime = new long[mCapacity]; - mClock = clock; - - } - - @VisibleForTesting - float getLux(int index) { - return mRingLux[offsetOf(index)]; - } - - @VisibleForTesting - float[] getAllLuxValues() { - float[] values = new float[mCount]; - if (mCount == 0) { - return values; - } - - if (mStart < mEnd) { - System.arraycopy(mRingLux, mStart, values, 0, mCount); - } else { - System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart); - System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd); - } - - return values; - } - - @VisibleForTesting - long getTime(int index) { - return mRingTime[offsetOf(index)]; - } - - @VisibleForTesting - long[] getAllTimestamps() { - long[] values = new long[mCount]; - if (mCount == 0) { - return values; - } - - if (mStart < mEnd) { - System.arraycopy(mRingTime, mStart, values, 0, mCount); - } else { - System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart); - System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd); - } - - return values; - } - - @VisibleForTesting - void push(long time, float lux) { - int next = mEnd; - if (mCount == mCapacity) { - int newSize = mCapacity * 2; - - float[] newRingLux = new float[newSize]; - long[] newRingTime = new long[newSize]; - int length = mCapacity - mStart; - System.arraycopy(mRingLux, mStart, newRingLux, 0, length); - System.arraycopy(mRingTime, mStart, newRingTime, 0, length); - if (mStart != 0) { - System.arraycopy(mRingLux, 0, newRingLux, length, mStart); - System.arraycopy(mRingTime, 0, newRingTime, length, mStart); - } - mRingLux = newRingLux; - mRingTime = newRingTime; - - next = mCapacity; - mCapacity = newSize; - mStart = 0; - } - mRingTime[next] = time; - mRingLux[next] = lux; - mEnd = next + 1; - if (mEnd == mCapacity) { - mEnd = 0; - } - mCount++; - } - - @VisibleForTesting - void prune(long horizon) { - if (mCount == 0) { - return; - } - - while (mCount > 1) { - int next = mStart + 1; - if (next >= mCapacity) { - next -= mCapacity; - } - if (mRingTime[next] > horizon) { - // Some light sensors only produce data upon a change in the ambient light - // levels, so we need to consider the previous measurement as the ambient light - // level for all points in time up until we receive a new measurement. Thus, we - // always want to keep the youngest element that would be removed from the - // buffer and just set its measurement time to the horizon time since at that - // point it is the ambient light level, and to remove it would be to drop a - // valid data point within our horizon. - break; - } - mStart = next; - mCount -= 1; - } - - if (mRingTime[mStart] < horizon) { - mRingTime[mStart] = horizon; - } - } - - @VisibleForTesting - int size() { - return mCount; - } - - @VisibleForTesting - void clear() { - mStart = 0; - mEnd = 0; - mCount = 0; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append('['); - for (int i = 0; i < mCount; i++) { - final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); - if (i != 0) { - buf.append(", "); - } - buf.append(getLux(i)); - buf.append(" / "); - buf.append(next - getTime(i)); - buf.append("ms"); - } - buf.append(']'); - return buf.toString(); - } - - private int offsetOf(int index) { - if (index >= mCount || index < 0) { - throw new ArrayIndexOutOfBoundsException(index); - } - index += mStart; - if (index >= mCapacity) { - index -= mCapacity; - } - return index; - } - } - - @VisibleForTesting - interface Injector { - Clock getClock(); - - Sensor getLightSensor(LightSensorControllerConfig config); - - boolean registerLightSensorListener( - SensorEventListener listener, Sensor sensor, int rate, Handler handler); - - void unregisterLightSensorListener(SensorEventListener listener); - - String getTag(); - - } - - private static class RealInjector implements Injector { - private final SensorManager mSensorManager; - private final int mSensorFallbackType; - - private final String mTag; - - private RealInjector(SensorManager sensorManager, int displayId) { - mSensorManager = sensorManager; - mSensorFallbackType = displayId == Display.DEFAULT_DISPLAY - ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK; - mTag = "LightSensorController [" + displayId + "]"; - } - - @Override - public Clock getClock() { - return Clock.SYSTEM_CLOCK; - } - - @Override - public Sensor getLightSensor(LightSensorControllerConfig config) { - return SensorUtils.findSensor( - mSensorManager, config.mAmbientLightSensor, mSensorFallbackType); - } - - @Override - public boolean registerLightSensorListener( - SensorEventListener listener, Sensor sensor, int rate, Handler handler) { - return mSensorManager.registerListener(listener, sensor, rate * 1000, handler); - } - - @Override - public void unregisterLightSensorListener(SensorEventListener listener) { - mSensorManager.unregisterListener(listener); - } - - @Override - public String getTag() { - return mTag; - } - } - - public static class LightSensorControllerConfig { - // Steady-state light sensor event rate in milliseconds. - private final int mNormalLightSensorRate; - private final int mInitialLightSensorRate; - - // If true immediately after the screen is turned on the controller will try to adjust the - // brightness based on the current sensor reads. If false, the controller will collect - // more data - // and only then decide whether to change brightness. - private final boolean mResetAmbientLuxAfterWarmUpConfig; - - // Period of time in which to consider light samples for a short/long-term estimate of - // ambient - // light in milliseconds. - private final int mAmbientLightHorizonShort; - private final int mAmbientLightHorizonLong; - - - // Amount of time to delay auto-brightness after screen on while waiting for - // the light sensor to warm-up in milliseconds. - // May be 0 if no warm-up is required. - private final int mLightSensorWarmUpTimeConfig; - - - // The intercept used for the weighting calculation. This is used in order to keep all - // possible - // weighting values positive. - private final int mWeightingIntercept; - - // Configuration object for determining thresholds to change brightness dynamically - private final HysteresisLevels mAmbientBrightnessThresholds; - private final HysteresisLevels mAmbientBrightnessThresholdsIdle; - - - // Stability requirements in milliseconds for accepting a new brightness level. This is - // used - // for debouncing the light sensor. Different constants are used to debounce the light - // sensor - // when adapting to brighter or darker environments. This parameter controls how quickly - // brightness changes occur in response to an observed change in light level that exceeds - // the - // hysteresis threshold. - private final long mBrighteningLightDebounceConfig; - private final long mDarkeningLightDebounceConfig; - private final long mBrighteningLightDebounceConfigIdle; - private final long mDarkeningLightDebounceConfigIdle; - - private final SensorData mAmbientLightSensor; - - @VisibleForTesting - LightSensorControllerConfig(int initialLightSensorRate, int normalLightSensorRate, - boolean resetAmbientLuxAfterWarmUpConfig, int ambientLightHorizonShort, - int ambientLightHorizonLong, int lightSensorWarmUpTimeConfig, - int weightingIntercept, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels ambientBrightnessThresholdsIdle, - long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, - long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, - SensorData ambientLightSensor) { - mInitialLightSensorRate = initialLightSensorRate; - mNormalLightSensorRate = normalLightSensorRate; - mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; - mAmbientLightHorizonShort = ambientLightHorizonShort; - mAmbientLightHorizonLong = ambientLightHorizonLong; - mLightSensorWarmUpTimeConfig = lightSensorWarmUpTimeConfig; - mWeightingIntercept = weightingIntercept; - mAmbientBrightnessThresholds = ambientBrightnessThresholds; - mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle; - mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; - mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; - mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle; - mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle; - mAmbientLightSensor = ambientLightSensor; - } - - private void dump(PrintWriter pw) { - pw.println("LightSensorControllerConfig:"); - pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate); - pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate); - pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); - pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); - pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); - pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); - pw.println(" mWeightingIntercept=" + mWeightingIntercept); - pw.println(" mAmbientBrightnessThresholds="); - mAmbientBrightnessThresholds.dump(pw); - pw.println(" mAmbientBrightnessThresholdsIdle="); - mAmbientBrightnessThresholdsIdle.dump(pw); - pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); - pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); - pw.println( - " mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle); - pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle); - pw.println(" mAmbientLightSensor=" + mAmbientLightSensor); - } - - /** - * Creates LightSensorControllerConfig object form Resources and DisplayDeviceConfig - */ - public static LightSensorControllerConfig create(Resources res, DisplayDeviceConfig ddc) { - int lightSensorRate = res.getInteger(R.integer.config_autoBrightnessLightSensorRate); - int initialLightSensorRate = res.getInteger( - R.integer.config_autoBrightnessInitialLightSensorRate); - if (initialLightSensorRate == -1) { - initialLightSensorRate = lightSensorRate; - } else if (initialLightSensorRate > lightSensorRate) { - Slog.w("LightSensorControllerConfig", - "Expected config_autoBrightnessInitialLightSensorRate (" - + initialLightSensorRate + ") to be less than or equal to " - + "config_autoBrightnessLightSensorRate (" + lightSensorRate - + ")."); - } - - boolean resetAmbientLuxAfterWarmUp = res.getBoolean( - R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); - int lightSensorWarmUpTimeConfig = res.getInteger( - R.integer.config_lightSensorWarmupTime); - - return new LightSensorControllerConfig(initialLightSensorRate, lightSensorRate, - resetAmbientLuxAfterWarmUp, ddc.getAmbientHorizonShort(), - ddc.getAmbientHorizonLong(), lightSensorWarmUpTimeConfig, - ddc.getAmbientHorizonLong(), - HysteresisLevels.getAmbientBrightnessThresholds(ddc), - HysteresisLevels.getAmbientBrightnessThresholdsIdle(ddc), - ddc.getAutoBrightnessBrighteningLightDebounce(), - ddc.getAutoBrightnessDarkeningLightDebounce(), - ddc.getAutoBrightnessBrighteningLightDebounceIdle(), - ddc.getAutoBrightnessDarkeningLightDebounceIdle(), - ddc.getAmbientLightSensor() - ); - } - } -} diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 9c7504db0cf0..a46975fb3567 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -285,7 +285,7 @@ public class BrightnessClamperController { List<BrightnessStateModifier> modifiers = new ArrayList<>(); modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); - if (flags.isEvenDimmerEnabled()) { + if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) { modifiers.add(new BrightnessLowLuxModifier(handler, listener, context, displayDeviceConfig)); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 095a233bde64..fd3da85e4437 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6272,9 +6272,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService final int[] userIds = resolveUserIds(UserHandle.USER_ALL); final String reason = "The mimeGroup is changed"; for (int i = 0; i < userIds.length; i++) { - final int packageUid = UserHandle.getUid(userIds[i], appId); - mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName, - true /* dontKillApp */, components, packageUid, reason); + final PackageUserStateInternal pkgUserState = + packageState.getUserStates().get(userIds[i]); + if (pkgUserState != null && pkgUserState.isInstalled()) { + final int packageUid = UserHandle.getUid(userIds[i], appId); + mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName, + true /* dontKillApp */, components, packageUid, reason); + } } }); } 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 8e3c6ac799b4..3a84897839a1 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -3844,6 +3844,7 @@ public class BatteryStatsImpl extends BatteryStats { public abstract T instantiateObject(); } + @SuppressWarnings("ParcelableCreator") public static class ControllerActivityCounterImpl extends ControllerActivityCounter implements Parcelable { private final Clock mClock; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 17e699668d14..257dadf6c02c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8507,8 +8507,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); - final boolean isFixedOrientationLetterboxAllowed = - parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW + // Bubble activities should always fill their parent and should not be letterboxed. + final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble() + && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW || parentWindowingMode == WINDOWING_MODE_FULLSCREEN // When starting to switch between PiP and fullscreen, the task is pinned // and the activity is fullscreen. But only allow to apply letterbox if the @@ -8516,7 +8517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || (!mWaitForEnteringPinnedMode && parentWindowingMode == WINDOWING_MODE_PINNED && resolvedConfig.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_FULLSCREEN); + == WINDOWING_MODE_FULLSCREEN)); // TODO(b/181207944): Consider removing the if condition and always run // resolveFixedOrientationConfiguration() since this should be applied for all cases. if (isFixedOrientationLetterboxAllowed) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 933633836e56..47f4a66995af 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -783,7 +783,7 @@ public class BackgroundActivityStartController { if (balShowToastsBlocked() && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) { // only show a toast if either caller or real caller could launch if they opted in - showToast("BAL blocked. go/debug-bal"); + showToast("BAL blocked. goo.gle/android-bal"); } return statsLog(BalVerdict.BLOCK, state); } diff --git a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp index c3375236098a..180081c9173a 100644 --- a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp +++ b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp @@ -32,10 +32,10 @@ namespace android { namespace { -// Max size we allow for the result from HIDIOCGRAWUNIQ (Bluetooth address or USB serial number). -// Copied from linux/hid.h struct hid_device->uniq char array size; the ioctl implementation -// writes at most this many bytes to the provided buffer. -constexpr int UNIQ_SIZE_MAX = 64; +// Max sizes we allow for results from string ioctl calls, copied from UAPI linux/uhid.h. +// The ioctl implementation writes at most this many bytes to the provided buffer: +constexpr int NAME_SIZE_MAX = 128; // HIDIOCGRAWNAME (device name) +constexpr int UNIQ_SIZE_MAX = 64; // HIDIOCGRAWUNIQ (BT address or USB serial number) } // anonymous namespace @@ -82,6 +82,16 @@ static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawB return info.bustype; } +static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawName( + JNIEnv* env, jclass /*clazz*/, int fd) { + char buf[NAME_SIZE_MAX]; + if (ioctl(fd, HIDIOCGRAWNAME(NAME_SIZE_MAX), buf) < 0) { + return nullptr; + } + // Local ref is not deleted because it is returned to Java + return env->NewStringUTF(buf); +} + static const JNINativeMethod gMethods[] = { {"nativeGetHidrawDescSize", "(I)I", (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawDescSize}, @@ -91,6 +101,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq}, {"nativeGetHidrawBusType", "(I)I", (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType}, + {"nativeGetHidrawName", "(I)Ljava/lang/String;", + (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawName}, }; int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f955b91136a3..c3175d64e2d3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -888,6 +888,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions"; private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true; + private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3; + /** * For apps targeting U+ * Enable multiple admins to coexist on the same device. @@ -21514,13 +21516,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slogf.w(LOG_TAG, "sourceUser and targetUser are the same, won't migrate account."); return; } - copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage); + + if (Flags.copyAccountWithRetryEnabled()) { + boolean copySucceeded = false; + int retryAttemptsLeft = RETRY_COPY_ACCOUNT_ATTEMPTS; + while (!copySucceeded && (retryAttemptsLeft > 0)) { + Slogf.i(LOG_TAG, "Copying account. Attempts left : " + retryAttemptsLeft); + copySucceeded = + copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage); + retryAttemptsLeft--; + } + } else { + copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage); + } if (!keepAccountMigrated) { removeAccount(accountToMigrate, sourceUserId); } + } - private void copyAccount( + private boolean copyAccount( UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate, String callerPackage) { final long startTime = SystemClock.elapsedRealtime(); @@ -21538,6 +21553,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_MS, startTime, callerPackage); + Slogf.i(LOG_TAG, "Copy account successful to " + targetUser); + return true; } else { logCopyAccountStatus(COPY_ACCOUNT_FAILED, callerPackage); Slogf.e(LOG_TAG, "Failed to copy account to " + targetUser); @@ -21550,6 +21567,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { logCopyAccountStatus(COPY_ACCOUNT_EXCEPTION, callerPackage); Slogf.e(LOG_TAG, "Exception copying account to " + targetUser, e); } + return false; } private static void logCopyAccountStatus(@CopyAccountStatus int status, String callerPackage) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index dd8757236a36..54de64e2f3a8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -22,7 +22,9 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyFloat; @@ -36,6 +38,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.hardware.Sensor; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; import android.os.PowerManager; @@ -47,8 +52,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.Clock; -import com.android.server.display.brightness.LightSensorController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.testutils.OffsettableClock; @@ -58,6 +61,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @@ -65,18 +69,31 @@ import org.mockito.MockitoAnnotations; public class AutomaticBrightnessControllerTest { private static final float BRIGHTNESS_MIN_FLOAT = 0.0f; private static final float BRIGHTNESS_MAX_FLOAT = 1.0f; + private static final int LIGHT_SENSOR_RATE = 20; private static final int INITIAL_LIGHT_SENSOR_RATE = 20; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 2000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000; private static final float DOZE_SCALE_FACTOR = 0.54f; + private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; + private static final int LIGHT_SENSOR_WARMUP_TIME = 0; + private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000; + private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000; private static final float EPSILON = 0.001f; private OffsettableClock mClock = new OffsettableClock(); private TestLooper mTestLooper; private Context mContext; private AutomaticBrightnessController mController; + private Sensor mLightSensor; + @Mock SensorManager mSensorManager; @Mock BrightnessMappingStrategy mBrightnessMappingStrategy; @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy; @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy; + @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; + @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle; @Mock HysteresisLevels mScreenBrightnessThresholdsIdle; @Mock Handler mNoOpHandler; @Mock BrightnessRangeController mBrightnessRangeController; @@ -84,18 +101,17 @@ public class AutomaticBrightnessControllerTest { BrightnessClamperController mBrightnessClamperController; @Mock BrightnessThrottler mBrightnessThrottler; - @Mock - LightSensorController mLightSensorController; - @Before public void setUp() throws Exception { // Share classloader to allow package private access. System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); + mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); mContext = InstrumentationRegistry.getContext(); setupController(BrightnessMappingStrategy.INVALID_LUX, - BrightnessMappingStrategy.INVALID_NITS); + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ false, + /* useHorizon= */ true); } @After @@ -107,7 +123,8 @@ public class AutomaticBrightnessControllerTest { } } - private void setupController(float userLux, float userNits) { + private void setupController(float userLux, float userNits, boolean applyDebounce, + boolean useHorizon) { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); @@ -130,22 +147,25 @@ public class AutomaticBrightnessControllerTest { } @Override - Clock createClock() { - return new Clock() { - @Override - public long uptimeMillis() { - return mClock.now(); - } - }; + AutomaticBrightnessController.Clock createClock() { + return mClock::now; } }, // pass in test looper instead, pass in offsettable clock - () -> { }, mTestLooper.getLooper(), - brightnessMappingStrategyMap, BRIGHTNESS_MIN_FLOAT, - BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, mScreenBrightnessThresholds, - mScreenBrightnessThresholdsIdle, + () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor, + brightnessMappingStrategyMap, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, + BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, + INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, + mAmbientBrightnessThresholds, mScreenBrightnessThresholds, + mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, - userLux, userNits, mLightSensorController, mBrightnessClamperController + useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1, + useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits, + mBrightnessClamperController ); when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn( @@ -166,15 +186,20 @@ public class AutomaticBrightnessControllerTest { @Test public void testNoHysteresisAtMinBrightness() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.02f as a brightness value float lux1 = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness1 = 0.02f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1)) + .thenReturn(lux1); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1)) + .thenReturn(lux1); when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt())) .thenReturn(normalizedBrightness1); @@ -185,31 +210,39 @@ public class AutomaticBrightnessControllerTest { .thenReturn(1.0f); // Send new sensor value and verify - listener.onAmbientLuxChange(lux1); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1)); assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON); // Set up system to return 0.0f (minimum possible brightness) as a brightness value float lux2 = 10.0f; float normalizedBrightness2 = 0.0f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2)) + .thenReturn(lux2); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2)) + .thenReturn(lux2); when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt())) .thenReturn(normalizedBrightness2); // Send new sensor value and verify - listener.onAmbientLuxChange(lux2); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2)); assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON); } @Test public void testNoHysteresisAtMaxBrightness() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.98f as a brightness value float lux1 = 100.0f; float normalizedBrightness1 = 0.98f; - + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1)) + .thenReturn(lux1); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1)) + .thenReturn(lux1); when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt())) .thenReturn(normalizedBrightness1); @@ -220,30 +253,35 @@ public class AutomaticBrightnessControllerTest { .thenReturn(1.1f); // Send new sensor value and verify - listener.onAmbientLuxChange(lux1); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1)); assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON); // Set up system to return 1.0f as a brightness value (brightness_max) float lux2 = 110.0f; float normalizedBrightness2 = 1.0f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2)) + .thenReturn(lux2); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2)) + .thenReturn(lux2); when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt())) .thenReturn(normalizedBrightness2); // Send new sensor value and verify - listener.onAmbientLuxChange(lux2); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2)); assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON); } @Test public void testUserAddUserDataPoint() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, @@ -260,11 +298,12 @@ public class AutomaticBrightnessControllerTest { public void testRecalculateSplines() throws Exception { // Enabling the light sensor, and setting the ambient lux to 1000 int currentLux = 1000; - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); - listener.onAmbientLuxChange(currentLux); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux)); // User sets brightness to 0.5f when(mBrightnessMappingStrategy.getBrightness(currentLux, @@ -294,13 +333,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelTimesOut() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, @@ -314,7 +354,7 @@ public class AutomaticBrightnessControllerTest { 123f, 0.5f)).thenReturn(true); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); mTestLooper.moveTimeForward( mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000); mTestLooper.dispatchAll(); @@ -333,13 +373,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelDoesntTimeOut() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, 0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */, @@ -358,7 +399,7 @@ public class AutomaticBrightnessControllerTest { mTestLooper.dispatchAll(); // Sensor reads 100000 lux, - listener.onAmbientLuxChange(678910); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910)); mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT); // Verify short term model is not reset. @@ -372,13 +413,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, @@ -398,7 +440,7 @@ public class AutomaticBrightnessControllerTest { 123f, 0.5f)).thenReturn(true); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); mTestLooper.moveTimeForward( mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000); mTestLooper.dispatchAll(); @@ -417,13 +459,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelNotRestoredAfterTimeout() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, @@ -445,7 +488,7 @@ public class AutomaticBrightnessControllerTest { 123f, 0.5f)).thenReturn(true); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // Do not fast-forward time. mTestLooper.dispatchAll(); @@ -463,13 +506,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testSwitchBetweenModesNoUserInteractions() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn( PowerManager.BRIGHTNESS_INVALID_FLOAT); @@ -485,7 +529,7 @@ public class AutomaticBrightnessControllerTest { BrightnessMappingStrategy.INVALID_LUX); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // Do not fast-forward time. mTestLooper.dispatchAll(); @@ -501,19 +545,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testSwitchToIdleMappingStrategy() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); - clearInvocations(mBrightnessMappingStrategy); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); - - - verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt()); - - clearInvocations(mBrightnessMappingStrategy); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, @@ -522,19 +561,22 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // There should be a user data point added to the mapper. - verify(mBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f, + verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f, /* brightness= */ 0.5f); - verify(mBrightnessMappingStrategy).setBrightnessConfiguration(any()); - verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt()); + verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any()); + verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt()); - clearInvocations(mBrightnessMappingStrategy); // Now let's do the same for idle mode mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE); - - verify(mBrightnessMappingStrategy).getMode(); - verify(mBrightnessMappingStrategy).getShortTermModelTimeout(); - verify(mBrightnessMappingStrategy).getUserBrightness(); - verify(mBrightnessMappingStrategy).getUserLux(); + // Called once when switching, + // setAmbientLux() is called twice and once in updateAutoBrightness(), + // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are + // called twice each. + verify(mBrightnessMappingStrategy, times(8)).getMode(); + // Called when switching. + verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout(); + verify(mBrightnessMappingStrategy, times(1)).getUserBrightness(); + verify(mBrightnessMappingStrategy, times(1)).getUserLux(); // Ensure, after switching, original BMS is not used anymore verifyNoMoreInteractions(mBrightnessMappingStrategy); @@ -546,25 +588,154 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Ensure we use the correct mapping strategy - verify(mIdleBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f, + verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f, /* brightness= */ 0.5f); } @Test + public void testAmbientLightHorizon() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + long increment = 500; + // set autobrightness to low + // t = 0 + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + + // t = 500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + + // t = 1000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 1500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // ensure that our reading is at 0. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // first 10000 lux sensor event reading + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3000 + // lux reading should still not yet be 10000. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3500 + mClock.fastForward(increment); + // lux has been high (10000) for 1000ms. + // lux reading should be 10000 + // short horizon (ambient lux) is high, long horizon is still not high + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4000 + // stay high + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4500 + Mockito.clearInvocations(mBrightnessMappingStrategy); + mClock.fastForward(increment); + // short horizon is high, long horizon is high too + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 5000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 5500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 6000 + mClock.fastForward(increment); + // ambient lux goes to 0 + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // only the values within the horizon should be kept + assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(), + EPSILON); + assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000}, + mController.getLastSensorTimestamps()); + } + + @Test + public void testHysteresisLevels() { + float[] ambientBrighteningThresholds = {50, 100}; + float[] ambientDarkeningThresholds = {10, 20}; + float[] ambientThresholdLevels = {0, 500}; + float ambientDarkeningMinChangeThreshold = 3.0f; + float ambientBrighteningMinChangeThreshold = 1.5f; + HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds, + ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels, + ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold); + + // test low, activate minimum change thresholds. + assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON); + assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON); + assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON); + + // test max + // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater + assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2); + assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON); + + // test just below threshold + assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON); + assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON); + + // test at (considered above) threshold + assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON); + assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON); + } + + @Test public void testBrightnessGetsThrottled() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return max brightness at 100 lux final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT; final float lux = 100.0f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)) + .thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)) + .thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt())) .thenReturn(normalizedBrightness); // Sensor reads 100 lux. We should get max brightness. - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f); assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f); @@ -592,6 +763,94 @@ public class AutomaticBrightnessControllerTest { } @Test + public void testGetSensorReadings() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Choose values such that the ring buffer's capacity is extended and the buffer is pruned + int increment = 11; + int lux = 5000; + for (int i = 0; i < 1000; i++) { + lux += increment; + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + } + + int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1); + float[] sensorValues = mController.getLastSensorValues(); + long[] sensorTimestamps = mController.getLastSensorTimestamps(); + + // Only the values within the horizon should be kept + assertEquals(valuesCount, sensorValues.length); + assertEquals(valuesCount, sensorTimestamps.length); + + long sensorTimestamp = mClock.now(); + for (int i = valuesCount - 1; i >= 1; i--) { + assertEquals(lux, sensorValues[i], EPSILON); + assertEquals(sensorTimestamp, sensorTimestamps[i]); + lux -= increment; + sensorTimestamp -= increment; + } + assertEquals(lux, sensorValues[0], EPSILON); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + } + + @Test + public void testGetSensorReadingsFullBuffer() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + int initialCapacity = 150; + + // Choose values such that the ring buffer is pruned + int increment1 = 200; + int lux = 5000; + for (int i = 0; i < 20; i++) { + lux += increment1; + mClock.fastForward(increment1); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + } + + int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1); + + // Choose values such that the buffer becomes full + int increment2 = 1; + for (int i = 0; i < initialCapacity - valuesCount; i++) { + lux += increment2; + mClock.fastForward(increment2); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + } + + float[] sensorValues = mController.getLastSensorValues(); + long[] sensorTimestamps = mController.getLastSensorTimestamps(); + + // The buffer should be full + assertEquals(initialCapacity, sensorValues.length); + assertEquals(initialCapacity, sensorTimestamps.length); + + long sensorTimestamp = mClock.now(); + for (int i = initialCapacity - 1; i >= 1; i--) { + assertEquals(lux, sensorValues[i], EPSILON); + assertEquals(sensorTimestamp, sensorTimestamps[i]); + + if (i >= valuesCount) { + lux -= increment2; + sensorTimestamp -= increment2; + } else { + lux -= increment1; + sensorTimestamp -= increment1; + } + } + assertEquals(lux, sensorValues[0], EPSILON); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + } + + @Test public void testResetShortTermModelWhenConfigChanges() { when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true); @@ -616,22 +875,179 @@ public class AutomaticBrightnessControllerTest { float userNits = 500; float userBrightness = 0.3f; when(mBrightnessMappingStrategy.getBrightnessFromNits(userNits)).thenReturn(userBrightness); - setupController(userLux, userNits); + setupController(userLux, userNits, /* applyDebounce= */ true, + /* useHorizon= */ false); verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness); } @Test + public void testBrighteningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholds.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // Lux isn't steady yet + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 4500 + // Lux is steady now + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testBrighteningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } + + @Test public void testBrightnessBasedOnLastObservedLux() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); // Send a new sensor value, disable the sensor and verify + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); mController.configure(AUTO_BRIGHTNESS_DISABLED, /* configuration= */ null, /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON, @@ -643,19 +1059,21 @@ public class AutomaticBrightnessControllerTest { @Test public void testAutoBrightnessInDoze() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); // Set policy to DOZE mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, @@ -664,7 +1082,7 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Send a new sensor value - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); // The brightness should be scaled by the doze factor assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, @@ -677,19 +1095,21 @@ public class AutomaticBrightnessControllerTest { @Test public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); // Switch mode to DOZE mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE); @@ -701,7 +1121,7 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Send a new sensor value - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); // The brightness should not be scaled by the doze factor assertEquals(normalizedBrightness, @@ -713,19 +1133,21 @@ public class AutomaticBrightnessControllerTest { @Test public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); // Set policy to DOZE mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, @@ -734,7 +1156,7 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Send a new sensor value - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); // The brightness should not be scaled by the doze factor assertEquals(normalizedBrightness, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index db2a1f46a891..afb87d1df798 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; @@ -78,8 +79,6 @@ import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; -import com.android.server.display.brightness.LightSensorController; -import com.android.server.display.brightness.TestUtilsKt; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; @@ -1167,19 +1166,30 @@ public final class DisplayPowerControllerTest { any(AutomaticBrightnessController.Callbacks.class), any(Looper.class), eq(mSensorManagerMock), + /* lightSensor= */ any(), /* brightnessMappingStrategyMap= */ any(SparseArray.class), + /* lightSensorWarmUpTime= */ anyInt(), /* brightnessMin= */ anyFloat(), /* brightnessMax= */ anyFloat(), /* dozeScaleFactor */ anyFloat(), + /* lightSensorRate= */ anyInt(), + /* initialLightSensorRate= */ anyInt(), + /* brighteningLightDebounceConfig */ anyLong(), + /* darkeningLightDebounceConfig */ anyLong(), + /* brighteningLightDebounceConfigIdle= */ anyLong(), + /* darkeningLightDebounceConfigIdle= */ anyLong(), + /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(), + any(HysteresisLevels.class), + any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), eq(mContext), any(BrightnessRangeController.class), any(BrightnessThrottler.class), + /* ambientLightHorizonShort= */ anyInt(), + /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(nits), - eq(DISPLAY_ID), - any(LightSensorController.LightSensorControllerConfig.class), any(BrightnessClamperController.class) ); } @@ -2148,22 +2158,22 @@ public final class DisplayPowerControllerTest { } @Override - LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig( - Context context, DisplayDeviceConfig displayDeviceConfig) { - return TestUtilsKt.createLightSensorControllerConfig(); - } - - @Override AutomaticBrightnessController getAutomaticBrightnessController( AutomaticBrightnessController.Callbacks callbacks, Looper looper, - SensorManager sensorManager, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, + HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessRangeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - int displayId, LightSensorController.LightSensorControllerConfig config, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { return mAutomaticBrightnessController; } @@ -2176,12 +2186,18 @@ public final class DisplayPowerControllerTest { } @Override - HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) { + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold) { return mHysteresisLevels; } @Override - HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) { + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold, boolean potentialOldBrightnessRange) { return mHysteresisLevels; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt b/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt deleted file mode 100644 index 02d6946aa30c..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.display - -import androidx.test.filters.SmallTest -import com.android.server.display.brightness.createHysteresisLevels -import kotlin.test.assertEquals -import org.junit.Test - -private const val FLOAT_TOLERANCE = 0.001f -@SmallTest -class HysteresisLevelsTest { - @Test - fun `test hysteresis levels`() { - val hysteresisLevels = createHysteresisLevels( - brighteningThresholdsPercentages = floatArrayOf(50f, 100f), - darkeningThresholdsPercentages = floatArrayOf(10f, 20f), - brighteningThresholdLevels = floatArrayOf(0f, 500f), - darkeningThresholdLevels = floatArrayOf(0f, 500f), - minDarkeningThreshold = 3f, - minBrighteningThreshold = 1.5f - ) - - // test low, activate minimum change thresholds. - assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), FLOAT_TOLERANCE) - assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), FLOAT_TOLERANCE) - assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), FLOAT_TOLERANCE) - - // test max - // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater - assertEquals( - 20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), FLOAT_TOLERANCE * 2) - assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), FLOAT_TOLERANCE) - - // test just below threshold - assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), FLOAT_TOLERANCE) - assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), FLOAT_TOLERANCE) - - // test at (considered above) threshold - assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), FLOAT_TOLERANCE) - assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), FLOAT_TOLERANCE) - } -}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt deleted file mode 100644 index 5fe91786eb24..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.display.brightness - -import androidx.test.filters.SmallTest -import com.android.internal.os.Clock -import com.android.server.display.brightness.LightSensorController.AmbientLightRingBuffer -import com.google.common.truth.Truth.assertThat -import org.junit.Assert.assertThrows -import org.junit.Test -import org.mockito.kotlin.mock - - -private const val BUFFER_INITIAL_CAPACITY = 3 - -@SmallTest -class AmbientLightRingBufferTest { - - private val buffer = AmbientLightRingBuffer(BUFFER_INITIAL_CAPACITY, mock<Clock>()) - - @Test - fun `test created empty`() { - assertThat(buffer.size()).isEqualTo(0) - } - - @Test - fun `test push to empty buffer`() { - buffer.push(1000, 0.5f) - - assertThat(buffer.size()).isEqualTo(1) - assertThat(buffer.getLux(0)).isEqualTo(0.5f) - assertThat(buffer.getTime(0)).isEqualTo(1000) - } - - @Test - fun `test prune keeps youngest outside horizon and sets time to horizon`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.prune(2500) - - assertThat(buffer.size()).isEqualTo(2) - - assertThat(buffer.getLux(0)).isEqualTo(0.6f) - assertThat(buffer.getTime(0)).isEqualTo(2500) - } - - @Test - fun `test prune keeps inside horizon`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.prune(2500) - - assertThat(buffer.size()).isEqualTo(2) - - assertThat(buffer.getLux(1)).isEqualTo(0.7f) - assertThat(buffer.getTime(1)).isEqualTo(3000) - } - - - @Test - fun `test pushes correctly after prune`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - buffer.prune(2500) - - buffer.push(4000, 0.8f) - - assertThat(buffer.size()).isEqualTo(3) - - assertThat(buffer.getLux(0)).isEqualTo(0.6f) - assertThat(buffer.getTime(0)).isEqualTo(2500) - assertThat(buffer.getLux(1)).isEqualTo(0.7f) - assertThat(buffer.getTime(1)).isEqualTo(3000) - assertThat(buffer.getLux(2)).isEqualTo(0.8f) - assertThat(buffer.getTime(2)).isEqualTo(4000) - } - - @Test - fun `test increase buffer size`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.push(4000, 0.8f) - - assertThat(buffer.size()).isEqualTo(4) - - assertThat(buffer.getLux(0)).isEqualTo(0.5f) - assertThat(buffer.getTime(0)).isEqualTo(1000) - assertThat(buffer.getLux(1)).isEqualTo(0.6f) - assertThat(buffer.getTime(1)).isEqualTo(2000) - assertThat(buffer.getLux(2)).isEqualTo(0.7f) - assertThat(buffer.getTime(2)).isEqualTo(3000) - assertThat(buffer.getLux(3)).isEqualTo(0.8f) - assertThat(buffer.getTime(3)).isEqualTo(4000) - } - - @Test - fun `test buffer clear`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.clear() - - assertThat(buffer.size()).isEqualTo(0) - assertThrows(ArrayIndexOutOfBoundsException::class.java) { - buffer.getLux(0) - } - } -}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt deleted file mode 100644 index 966134aa2b77..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt +++ /dev/null @@ -1,432 +0,0 @@ -/* - * 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.display.brightness - -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.os.Handler -import androidx.test.filters.SmallTest -import com.android.internal.os.Clock -import com.android.server.display.TestUtils -import com.android.server.display.brightness.LightSensorController.Injector -import com.android.server.display.brightness.LightSensorController.LightSensorControllerConfig -import com.android.server.testutils.OffsettableClock -import com.android.server.testutils.TestHandler -import com.google.common.truth.Truth.assertThat -import kotlin.math.ceil -import kotlin.test.assertEquals -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -private const val FLOAT_TOLERANCE = 0.001f - -@SmallTest -class LightSensorControllerTest { - - private val testHandler = TestHandler(null) - private val testInjector = TestInjector() - - @Test - fun `test ambient light horizon`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - brighteningLightDebounceConfig = 0, - darkeningLightDebounceConfig = 0, - ambientLightHorizonShort = 1000, - ambientLightHorizonLong = 2000 - ), testInjector, testHandler) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - val timeIncrement = 500L - // set ambient lux to low - // t = 0 - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - - // t = 500 - // - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - - // t = 1000 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 1500 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 2000 - // ensure that our reading is at 0. - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 2500 - // first 10000 lux sensor event reading - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 3000 - // lux reading should still not yet be 10000. - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertTrue(reportedAmbientLux > 0) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 3500 - testInjector.clock.fastForward(timeIncrement) - // at short horizon, first value outside will be used in calculation (t = 2000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 4000 - // lux has been high (10000) for more than 1000ms. - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 4500 - testInjector.clock.fastForward(timeIncrement) - // short horizon is high, long horizon is high too - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 5000 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 5500 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 6000 - testInjector.clock.fastForward(timeIncrement) - // at short horizon, first value outside will be used in calculation (t = 4500) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 6500 - testInjector.clock.fastForward(timeIncrement) - // ambient lux goes to 0 - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // only the values within the horizon should be kept - assertArrayEquals(floatArrayOf(10_000f, 0f, 0f, 0f, 0f), - lightSensorController.lastSensorValues, FLOAT_TOLERANCE) - assertArrayEquals(longArrayOf(4_500, 5_000, 5_500, 6_000, 6_500), - lightSensorController.lastSensorTimestamps) - } - - @Test - fun `test brightening debounce`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - brighteningLightDebounceConfig = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // brightening threshold is set to previous lux value - ambientBrightnessThresholds = createHysteresisLevels( - brighteningThresholdLevels = floatArrayOf(), - brighteningThresholdsPercentages = floatArrayOf(), - minBrighteningThreshold = 0f - ) - ), testInjector, testHandler) - lightSensorController.setIdleMode(false) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial brightening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux increase, first brightening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < brighteningLightDebounceConfig) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < brighteningLightDebounceConfig) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f)) - assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - @Test - fun `test sensor readings`() { - val ambientLightHorizonLong = 2_500 - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - ambientLightHorizonLong = ambientLightHorizonLong - ), testInjector, testHandler) - lightSensorController.setListener { } - lightSensorController.setIdleMode(false) - lightSensorController.enableLightSensorIfNeeded() - - // Choose values such that the ring buffer's capacity is extended and the buffer is pruned - val increment = 11 - var lux = 5000 - for (i in 0 until 1000) { - lux += increment - testInjector.clock.fastForward(increment.toLong()) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(lux.toFloat())) - } - - val valuesCount = ceil(ambientLightHorizonLong.toDouble() / increment + 1).toInt() - val sensorValues = lightSensorController.lastSensorValues - val sensorTimestamps = lightSensorController.lastSensorTimestamps - - // Only the values within the horizon should be kept - assertEquals(valuesCount, sensorValues.size) - assertEquals(valuesCount, sensorTimestamps.size) - - var sensorTimestamp = testInjector.clock.now() - for (i in valuesCount - 1 downTo 1) { - assertEquals(lux.toFloat(), sensorValues[i], FLOAT_TOLERANCE) - assertEquals(sensorTimestamp, sensorTimestamps[i]) - lux -= increment - sensorTimestamp -= increment - } - assertEquals(lux.toFloat(), sensorValues[0], FLOAT_TOLERANCE) - assertEquals(testInjector.clock.now() - ambientLightHorizonLong, sensorTimestamps[0]) - } - - @Test - fun `test darkening debounce`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - darkeningLightDebounceConfig = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // darkening threshold is set to previous lux value - ambientBrightnessThresholds = createHysteresisLevels( - darkeningThresholdLevels = floatArrayOf(), - darkeningThresholdsPercentages = floatArrayOf(), - minDarkeningThreshold = 0f - ) - ), testInjector, testHandler) - - lightSensorController.setIdleMode(false) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial darkening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux decreased, first darkening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < darkeningLightDebounceConfig) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < darkeningLightDebounceConfig) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f)) - assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - @Test - fun `test brightening debounce in idle mode`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - brighteningLightDebounceConfigIdle = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // brightening threshold is set to previous lux value - ambientBrightnessThresholdsIdle = createHysteresisLevels( - brighteningThresholdLevels = floatArrayOf(), - brighteningThresholdsPercentages = floatArrayOf(), - minBrighteningThreshold = 0f - ) - ), testInjector, testHandler) - lightSensorController.setIdleMode(true) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial brightening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux increase, first brightening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < brighteningLightDebounceConfigIdle) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < brighteningLightDebounceConfigIdle) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f)) - assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - @Test - fun `test darkening debounce in idle mode`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - darkeningLightDebounceConfigIdle = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // darkening threshold is set to previous lux value - ambientBrightnessThresholdsIdle = createHysteresisLevels( - darkeningThresholdLevels = floatArrayOf(), - darkeningThresholdsPercentages = floatArrayOf(), - minDarkeningThreshold = 0f - ) - ), testInjector, testHandler) - - lightSensorController.setIdleMode(true) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial darkening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux decreased, first darkening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < darkeningLightDebounceConfigIdle) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < darkeningLightDebounceConfigIdle) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f)) - assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - - private fun sensorEvent(value: Float) = SensorEvent( - testInjector.testSensor, 0, 0, floatArrayOf(value) - ) - - private class TestInjector : Injector { - val testSensor: Sensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor") - val clock: OffsettableClock = OffsettableClock.Stopped() - - var sensorEventListener: SensorEventListener? = null - override fun getClock(): Clock { - return object : Clock() { - override fun uptimeMillis(): Long { - return clock.now() - } - } - } - - override fun getLightSensor(config: LightSensorControllerConfig): Sensor { - return testSensor - } - - override fun registerLightSensorListener( - listener: SensorEventListener, - sensor: Sensor, - rate: Int, - handler: Handler - ): Boolean { - sensorEventListener = listener - return true - } - - override fun unregisterLightSensorListener(listener: SensorEventListener) { - sensorEventListener = null - } - - override fun getTag(): String { - return "LightSensorControllerTest" - } - } -}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt deleted file mode 100644 index 1328f5fa0e90..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.display.brightness - -import com.android.server.display.HysteresisLevels -import com.android.server.display.config.SensorData - -@JvmOverloads -fun createLightSensorControllerConfig( - initialSensorRate: Int = 1, - normalSensorRate: Int = 2, - resetAmbientLuxAfterWarmUpConfig: Boolean = true, - ambientLightHorizonShort: Int = 1, - ambientLightHorizonLong: Int = 10_000, - lightSensorWarmUpTimeConfig: Int = 0, - weightingIntercept: Int = 10_000, - ambientBrightnessThresholds: HysteresisLevels = createHysteresisLevels(), - ambientBrightnessThresholdsIdle: HysteresisLevels = createHysteresisLevels(), - brighteningLightDebounceConfig: Long = 100_000, - darkeningLightDebounceConfig: Long = 100_000, - brighteningLightDebounceConfigIdle: Long = 100_000, - darkeningLightDebounceConfigIdle: Long = 100_000, - ambientLightSensor: SensorData = SensorData() -) = LightSensorController.LightSensorControllerConfig( - initialSensorRate, - normalSensorRate, - resetAmbientLuxAfterWarmUpConfig, - ambientLightHorizonShort, - ambientLightHorizonLong, - lightSensorWarmUpTimeConfig, - weightingIntercept, - ambientBrightnessThresholds, - ambientBrightnessThresholdsIdle, - brighteningLightDebounceConfig, - darkeningLightDebounceConfig, - brighteningLightDebounceConfigIdle, - darkeningLightDebounceConfigIdle, - ambientLightSensor -) - -fun createHysteresisLevels( - brighteningThresholdsPercentages: FloatArray = floatArrayOf(), - darkeningThresholdsPercentages: FloatArray = floatArrayOf(), - brighteningThresholdLevels: FloatArray = floatArrayOf(), - darkeningThresholdLevels: FloatArray = floatArrayOf(), - minDarkeningThreshold: Float = 0f, - minBrighteningThreshold: Float = 0f, - potentialOldBrightnessRange: Boolean = false -) = HysteresisLevels( - brighteningThresholdsPercentages, - darkeningThresholdsPercentages, - brighteningThresholdLevels, - darkeningThresholdLevels, - minDarkeningThreshold, - minBrighteningThreshold, - potentialOldBrightnessRange -)
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java index 344e2c21f0a5..69a98ace1c33 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.BrailleDisplayController; +import android.accessibilityservice.IBrailleDisplayController; import android.content.Context; import android.os.Bundle; import android.os.IBinder; @@ -174,6 +175,17 @@ public class BrailleDisplayConnectionTest { } @Test + public void defaultNativeScanner_getName_returnsName() { + String name = "My Braille Display"; + when(mNativeInterface.getHidrawName(anyInt())).thenReturn(name); + + BrailleDisplayConnection.BrailleDisplayScanner scanner = + BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); + + assertThat(scanner.getName(NULL_PATH)).isEqualTo(name); + } + + @Test public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() { Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); mBrailleDisplayConnection.write( @@ -201,6 +213,38 @@ public class BrailleDisplayConnectionTest { verify(mBrailleDisplayConnection).disconnect(); } + @Test + public void connect_unableToGetUniq_usesNameFallback() throws Exception { + try { + IBrailleDisplayController controller = + Mockito.mock(IBrailleDisplayController.class); + final Path path = Path.of("/dev/null"); + final String macAddress = "00:11:22:33:AA:BB"; + final String name = "My Braille Display"; + final byte[] descriptor = {0x05, 0x41}; + Bundle bd = new Bundle(); + bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, + path.toString()); + bd.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, + descriptor); + bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name); + bd.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, true); + bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, null); + BrailleDisplayConnection.BrailleDisplayScanner scanner = + mBrailleDisplayConnection.setTestData(List.of(bd)); + // Validate that the test data is set up correctly before attempting connection: + assertThat(scanner.getUniqueId(path)).isNull(); + assertThat(scanner.getName(path)).isEqualTo(name); + + mBrailleDisplayConnection.connectLocked( + macAddress, name, BrailleDisplayConnection.BUS_BLUETOOTH, controller); + + verify(controller).onConnected(eq(mBrailleDisplayConnection), eq(descriptor)); + } finally { + mBrailleDisplayConnection.disconnect(); + } + } + // BrailleDisplayConnection#setTestData() is used to enable CTS testing with // test Braille display data, but its own implementation should also be tested // so that issues in this helper don't cause confusing failures in CTS. @@ -220,6 +264,9 @@ public class BrailleDisplayConnectionTest { String uniq1 = "uniq1", uniq2 = "uniq2"; bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1); bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2); + String name1 = "name1", name2 = "name2"; + bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name1); + bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name2); int bus1 = BrailleDisplayConnection.BUS_USB, bus2 = BrailleDisplayConnection.BUS_BLUETOOTH; bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, @@ -235,6 +282,8 @@ public class BrailleDisplayConnectionTest { expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2); expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1); expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2); + expect.that(scanner.getName(path1)).isEqualTo(name1); + expect.that(scanner.getName(path2)).isEqualTo(name2); expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1); expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2); } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 643dcec27bfd..0a2a855e9317 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -123,6 +123,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -678,6 +679,87 @@ public class UserControllerTest { } /** + * Test that, when exceeding the maximum number of running users, a profile of the current user + * is not stopped. + */ + @Test + public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped() + throws Exception { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false); + + final int PARENT_ID = 200; + final int PROFILE1_ID = 201; + final int PROFILE2_ID = 202; + final int FG_USER_ID = 300; + final int BG_USER_ID = 400; + + setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID; + setUpUser(PROFILE1_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID; + setUpUser(PROFILE2_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID; + setUpUser(FG_USER_ID, 0).profileGroupId = FG_USER_ID; + setUpUser(BG_USER_ID, 0).profileGroupId = UserInfo.NO_PROFILE_GROUP_ID; + mUserController.onSystemReady(); // To set the profileGroupIds in UserController. + + assertEquals(newHashSet( + SYSTEM_USER_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + int numberOfUserSwitches = 1; + addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM, + numberOfUserSwitches, false); + mUserController.finishUserSwitch(mUserStates.get(PARENT_ID)); + waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); + assertTrue(mUserController.canStartMoreUsers()); + assertEquals(newHashSet( + SYSTEM_USER_ID, PARENT_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + assertThat(mUserController.startProfile(PROFILE1_ID, true, null)).isTrue(); + assertEquals(newHashSet( + SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + numberOfUserSwitches++; + addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID, + numberOfUserSwitches, false); + mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID)); + waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); + assertTrue(mUserController.canStartMoreUsers()); + assertEquals(newHashSet( + SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND); + assertEquals(newHashSet( + SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, BG_USER_ID, FG_USER_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + + // Now we exceed the maxRunningUsers parameter (of 5): + assertThat(mUserController.startProfile(PROFILE2_ID, true, null)).isTrue(); + // Currently, starting a profile doesn't trigger evaluating whether we've exceeded max, so + // we expect no users to be stopped. This policy may change in the future. Log but no fail. + if (!newHashSet(SYSTEM_USER_ID, PROFILE1_ID, BG_USER_ID, PROFILE2_ID, PARENT_ID, FG_USER_ID) + .equals(new HashSet<>(mUserController.getRunningUsersLU()))) { + Log.w(TAG, "Starting a profile that exceeded max running users didn't lead to " + + "expectations: " + mUserController.getRunningUsersLU()); + } + + numberOfUserSwitches++; + addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID, + numberOfUserSwitches, false); + mUserController.finishUserSwitch(mUserStates.get(PARENT_ID)); + waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); + // We've now done a user switch and should notice that we've exceeded the maximum number of + // users. The oldest background user should be stopped (BG_USER); even though PROFILE1 was + // older, it should not be stopped since it's a profile of the (new) current user. + assertFalse(mUserController.canStartMoreUsers()); + assertEquals(newHashSet( + SYSTEM_USER_ID, PROFILE1_ID, PROFILE2_ID, FG_USER_ID, PARENT_ID), + new HashSet<>(mUserController.getRunningUsersLU())); + } + + /** * Test that, in getRunningUsersLU, parents come after their profile, even if the profile was * started afterwards. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 85172e08423e..856ad2a02444 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -928,6 +928,17 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testIsLetterboxed_activityFromBubble_returnsFalse() { + setUpDisplaySizeWithApp(1000, 2500); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + spyOn(mActivity); + doReturn(true).when(mActivity).getLaunchedFromBubble(); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + assertFalse(mActivity.areBoundsLetterboxed()); + } + + @Test public void testAspectRatioMatchParentBoundsAndImeAttachable() { setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2000).build()); prepareUnresizable(mActivity, 2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 55a00fc0ec89..48fc2dc4bada 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -799,6 +799,7 @@ public class WindowContainerTests extends WindowTestsBase { verify(child).onConfigurationChanged(any()); } + @SuppressWarnings("SelfComparison") @Test public void testCompareTo() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index df32fbd2e58d..ed3d5d546f72 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9854,6 +9854,19 @@ public class CarrierConfigManager { "satellite_entitlement_supported_bool"; /** + * Indicates the appName that is used when querying the entitlement server for satellite. + * + * The default value is androidSatmode. + * + * Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management. + * `app_name` is an optional attribute in the request and may vary depending on the carrier + * requirement. + * @hide + */ + public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = + "satellite_entitlement_app_name_string"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -10997,6 +11010,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true); sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30); sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false); + sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode"); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); |