diff options
490 files changed, 12834 insertions, 4536 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index f249884cb1a0..2ce3221e56cf 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -102,6 +102,7 @@ aconfig_declarations_group { "com.android.media.flags.projection-aconfig-java", "com.android.net.http.flags-aconfig-exported-java", "com.android.net.thread.platform.flags-aconfig-java", + "com.android.permission.flags-aconfig-java-export", "com.android.ranging.flags.ranging-aconfig-java-export", "com.android.server.contextualsearch.flags-java", "com.android.server.flags.services-aconfig-java", @@ -115,6 +116,7 @@ aconfig_declarations_group { "framework-jobscheduler-job.flags-aconfig-java", "framework_graphics_flags_java_lib", "hwui_flags_java_lib", + "icu_exported_aconfig_flags_lib", "interaction_jank_monitor_flags_lib", "keystore2_flags_java-framework", "libcore_exported_aconfig_flags_lib", @@ -163,6 +165,14 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// ICU +java_aconfig_library { + name: "icu_exported_aconfig_flags_lib", + aconfig_declarations: "icu_aconfig_flags", + mode: "exported", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Camera java_aconfig_library { name: "camera_platform_flags_core_java_lib", @@ -1409,6 +1419,7 @@ java_aconfig_library { // Content Capture aconfig_declarations { name: "android.view.contentcapture.flags-aconfig", + exportable: true, package: "android.view.contentcapture.flags", container: "system", srcs: ["core/java/android/view/contentcapture/flags/*.aconfig"], @@ -16,6 +16,7 @@ omakoto@google.com #{LAST_RESORT_SUGGESTION} roosa@google.com #{LAST_RESORT_SUGGESTION} smoreland@google.com #{LAST_RESORT_SUGGESTION} yamasani@google.com #{LAST_RESORT_SUGGESTION} +timmurray@google.com #{LAST_RESORT_SUGGESTION} # API changes are already covered by API-Review+1 (http://mdb/android-api-council) # via https://android.git.corp.google.com/All-Projects/+/refs/meta/config/rules.pl. diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index a949ff5a331b..787fdee6ee16 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -1023,7 +1023,6 @@ stubs_defaults { api_levels_annotations_enabled: true, api_levels_annotations_dirs: [ "sdk-dir", - "api-versions-jars-dir", ], } diff --git a/cmds/am/am.sh b/cmds/am/am.sh index 54c2d394be2c..76ec214cb446 100755 --- a/cmds/am/am.sh +++ b/cmds/am/am.sh @@ -1,5 +1,8 @@ #!/system/bin/sh +# set to top-app process group +settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true + if [ "$1" != "instrument" ] ; then cmd activity "$@" else diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f44448a8c311..15ae79e34061 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3165,6 +3165,11 @@ package android.app.wallpaper { method @NonNull public android.util.SparseArray<android.graphics.Rect> getCropHints(); } + public static final class WallpaperDescription.Builder { + method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>); + method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull android.util.SparseArray<android.graphics.Rect>); + } + } package android.app.wallpapereffectsgeneration { @@ -3457,7 +3462,7 @@ package android.companion.virtual { method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method public void setDevicePolicy(int, int); method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void setDevicePolicy(int, int, int); - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public void setDisplayImePolicy(int, int); + method public void setDisplayImePolicy(int, int); method public void setShowPointerIcon(boolean); method public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void wakeUp(); @@ -3476,7 +3481,7 @@ package android.companion.virtual { method public int getDevicePolicy(int); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration(); method @Nullable public android.content.ComponentName getHomeComponent(); - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent(); + method @Nullable public android.content.ComponentName getInputMethodComponent(); method public int getLockState(); method @Nullable public String getName(); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout(); @@ -3515,7 +3520,7 @@ package android.companion.virtual { method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName); - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String); method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration); @@ -19038,7 +19043,7 @@ package android.view.displayhash { package android.view.inputmethod { public final class InputMethodInfo implements android.os.Parcelable { - method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public boolean isVirtualDeviceOnly(); + method public boolean isVirtualDeviceOnly(); } public final class InputMethodManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index eb483c82b450..0126db70296c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -881,15 +881,6 @@ package android.app.usage { } -package android.app.wallpaper { - - public static final class WallpaperDescription.Builder { - method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>); - method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull android.util.SparseArray<android.graphics.Rect>); - } - -} - package android.appwidget { public class AppWidgetManager { diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index 4ac40a1f77b2..c597a9dcae7e 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -234,4 +234,19 @@ public class DreamManager { throw e.rethrowFromSystemServer(); } } + + /** + * Notifies dream manager of device postured state, which may affect dream enablement. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_DREAM_WHEN_POSTURED) + @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) + public void setDevicePostured(boolean isPostured) { + try { + mService.setDevicePostured(isPostured); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 1738a92b7672..4a78d01783f8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -271,6 +271,6 @@ interface INotificationManager int[] getAllowedAdjustmentKeyTypes(); void setAssistantAdjustmentKeyTypeState(int type, boolean enabled); - int[] getAllowedAdjustmentKeyTypesForPackage(String pkg); - void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type, boolean enabled); + String[] getTypeAdjustmentDeniedPackages(); + void setTypeAdjustmentForPackageState(String pkg, boolean enabled); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 24f2495d8f09..e5d80de24f2b 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2163,12 +2163,10 @@ public class NotificationManager { * @hide */ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void setAssistantAdjustmentKeyTypeStateForPackage(@NonNull String pkg, - @Adjustment.Types int type, - boolean enabled) { + public void setTypeAdjustmentForPackageState(@NonNull String pkg, boolean enabled) { INotificationManager service = service(); try { - service.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled); + service.setTypeAdjustmentForPackageState(pkg, enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/supervision/ISupervisionAppService.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl index 033998fc4a5b..207fab90d015 100644 --- a/core/java/android/app/supervision/ISupervisionAppService.aidl +++ b/core/java/android/app/supervision/ISupervisionAppService.aidl @@ -20,4 +20,6 @@ package android.app.supervision; * @hide */ interface ISupervisionAppService { + void onEnabled(); + void onDisabled(); } diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java index 4468c78cbd34..4530be5c270a 100644 --- a/core/java/android/app/supervision/SupervisionAppService.java +++ b/core/java/android/app/supervision/SupervisionAppService.java @@ -28,10 +28,29 @@ import android.os.IBinder; */ public class SupervisionAppService extends Service { private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() { + @Override + public void onEnabled() { + SupervisionAppService.this.onEnabled(); + } + + @Override + public void onDisabled() { + SupervisionAppService.this.onDisabled(); + } }; @Override public final IBinder onBind(Intent intent) { return mBinder.asBinder(); } + + /** + * Called when supervision is enabled. + */ + public void onEnabled() {} + + /** + * Called when supervision is disabled. + */ + public void onDisabled() {} } diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index a5b58f968c27..92241f3634e8 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -34,6 +34,35 @@ public class SupervisionManager { private final Context mContext; private final ISupervisionManager mService; + /** + * Activity action: ask the human user to enable supervision for this user. Only the app that + * holds the {@code SYSTEM_SUPERVISION} role can launch this intent. + * + * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the + * result of whether or not the user approved the action. If approved, the result will be {@link + * Activity#RESULT_OK}. + * + * <p>If supervision is already enabled, the operation will return a failure result. + * + * @hide + */ + public static final String ACTION_ENABLE_SUPERVISION = "android.app.action.ENABLE_SUPERVISION"; + + /** + * Activity action: ask the human user to disable supervision for this user. Only the app that + * holds the {@code SYSTEM_SUPERVISION} role can launch this intent. + * + * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the + * result of whether or not the user approved the action. If approved, the result will be {@link + * Activity#RESULT_OK}. + * + * <p>If supervision is not enabled, the operation will return a failure result. + * + * @hide + */ + public static final String ACTION_DISABLE_SUPERVISION = + "android.app.action.DISABLE_SUPERVISION"; + /** @hide */ @UnsupportedAppUsage public SupervisionManager(Context context, ISupervisionManager service) { diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index 999a5da870ad..a13af7f1ddcd 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -19,9 +19,7 @@ package android.app.wallpaper; import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; import android.annotation.FlaggedApi; -import android.annotation.SuppressLint; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.WallpaperManager.ScreenOrientation; @@ -514,8 +512,7 @@ public final class WallpaperDescription implements Parcelable { * @hide */ @NonNull - @TestApi - @SuppressLint("MissingGetterMatchingBuilder") + @SystemApi public Builder setCropHints(@NonNull Map<Point, Rect> cropHints) { mCropHints = new SparseArray<>(); cropHints.forEach( @@ -531,8 +528,7 @@ public final class WallpaperDescription implements Parcelable { * @hide */ @NonNull - @TestApi - @SuppressLint("MissingGetterMatchingBuilder") + @SystemApi public Builder setCropHints(@NonNull SparseArray<Rect> cropHints) { mCropHints = cropHints; return this; diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 2f161150a89b..ceafce2bdbb7 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -287,8 +287,8 @@ public final class AssociationInfo implements Parcelable { /** * Get the device icon of the associated device. The device icon represents the device type. * - * @return the device icon, or {@code null} if no device icon has been set for the - * associated device. + * @return the device icon with size 24dp x 24dp. + * If the associated device has no icon set, it returns {@code null}. * * @see AssociationRequest.Builder#setDeviceIcon(Icon) */ @@ -377,6 +377,7 @@ public final class AssociationInfo implements Parcelable { if (this == o) return true; if (!(o instanceof AssociationInfo)) return false; final AssociationInfo that = (AssociationInfo) o; + return mId == that.mId && mUserId == that.mUserId && mSelfManaged == that.mSelfManaged @@ -391,11 +392,17 @@ public final class AssociationInfo implements Parcelable { && Objects.equals(mDeviceProfile, that.mDeviceProfile) && Objects.equals(mAssociatedDevice, that.mAssociatedDevice) && mSystemDataSyncFlags == that.mSystemDataSyncFlags - && (mDeviceIcon == null ? that.mDeviceIcon == null - : mDeviceIcon.sameAs(that.mDeviceIcon)) + && isSameIcon(mDeviceIcon, that.mDeviceIcon) && Objects.equals(mDeviceId, that.mDeviceId); } + private boolean isSameIcon(Icon iconA, Icon iconB) { + // Because we've already rescaled and converted both icons to bitmaps, + // we can now directly compare them by bitmap. + return (iconA == null && iconB == null) + || (iconA != null && iconB != null && iconA.getBitmap().sameAs(iconB.getBitmap())); + } + @Override public int hashCode() { return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, @@ -425,7 +432,7 @@ public final class AssociationInfo implements Parcelable { dest.writeLong(mTimeApprovedMs); dest.writeLong(mLastTimeConnectedMs); dest.writeInt(mSystemDataSyncFlags); - if (mDeviceIcon != null) { + if (Flags.associationDeviceIcon() && mDeviceIcon != null) { dest.writeInt(1); mDeviceIcon.writeToParcel(dest, flags); } else { @@ -455,7 +462,8 @@ public final class AssociationInfo implements Parcelable { mTimeApprovedMs = in.readLong(); mLastTimeConnectedMs = in.readLong(); mSystemDataSyncFlags = in.readInt(); - if (in.readInt() == 1) { + int deviceIcon = in.readInt(); + if (Flags.associationDeviceIcon() && deviceIcon == 1) { mDeviceIcon = Icon.CREATOR.createFromParcel(in); } else { mDeviceIcon = null; diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 32cbf326c923..a098a6067491 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -385,6 +385,10 @@ public final class AssociationRequest implements Parcelable { public void setAssociatedDevice(AssociatedDevice associatedDevice) { mAssociatedDevice = associatedDevice; } + /** @hide */ + public void setDeviceIcon(Icon deviceIcon) { + mDeviceIcon = deviceIcon; + } /** @hide */ @NonNull @@ -492,9 +496,10 @@ public final class AssociationRequest implements Parcelable { /** * Set the device icon for the self-managed device and to display the icon in the * self-managed association dialog. + * <p>The given device icon will be resized to 24dp x 24dp. * - * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp - * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}. + * @throws IllegalArgumentException if the icon is + * {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}. * @see #setSelfManaged(boolean) */ @NonNull diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index a96ba11eb482..566e78a8de35 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -23,7 +23,6 @@ import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; - import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -52,10 +51,10 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; -import android.graphics.drawable.VectorDrawable; import android.net.MacAddress; import android.os.Binder; import android.os.Handler; @@ -110,6 +109,7 @@ import java.util.function.Consumer; @RequiresFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP) public final class CompanionDeviceManager { private static final String TAG = "CDM_CompanionDeviceManager"; + private static final int ICON_TARGET_SIZE = 24; /** @hide */ @IntDef(prefix = {"RESULT_"}, value = { @@ -474,10 +474,8 @@ public final class CompanionDeviceManager { if (Flags.associationDeviceIcon()) { final Icon deviceIcon = request.getDeviceIcon(); - - if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) { - throw new IllegalArgumentException("The size of the device icon must be " - + "24dp x 24dp to ensure proper display"); + if (deviceIcon != null) { + request.setDeviceIcon(scaleIcon(deviceIcon, mContext)); } } @@ -547,10 +545,8 @@ public final class CompanionDeviceManager { if (Flags.associationDeviceIcon()) { final Icon deviceIcon = request.getDeviceIcon(); - - if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) { - throw new IllegalArgumentException("The size of the device icon must be " - + "24dp x 24dp to ensure proper display"); + if (deviceIcon != null) { + request.setDeviceIcon(scaleIcon(deviceIcon, mContext)); } } @@ -2024,33 +2020,26 @@ public final class CompanionDeviceManager { } } - private boolean isValidIcon(Icon icon, Context context) { + private Icon scaleIcon(Icon icon, Context context) { + if (icon == null) return null; if (icon.getType() == TYPE_URI_ADAPTIVE_BITMAP || icon.getType() == TYPE_URI) { throw new IllegalArgumentException("The URI based Icon is not supported."); } - Drawable drawable = icon.loadDrawable(context); - float density = context.getResources().getDisplayMetrics().density; + Bitmap bitmap; + Drawable drawable = icon.loadDrawable(context); if (drawable instanceof BitmapDrawable) { - Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); - - float widthDp = bitmap.getWidth() / density; - float heightDp = bitmap.getHeight() / density; - - if (widthDp != 24 || heightDp != 24) { - return false; - } - } else if (drawable instanceof VectorDrawable) { - VectorDrawable vectorDrawable = (VectorDrawable) drawable; - float widthDp = vectorDrawable.getIntrinsicWidth() / density; - float heightDp = vectorDrawable.getIntrinsicHeight() / density; - - if (widthDp != 24 || heightDp != 24) { - return false; - } + bitmap = Bitmap.createScaledBitmap( + ((BitmapDrawable) drawable).getBitmap(), ICON_TARGET_SIZE, ICON_TARGET_SIZE, + false); } else { - throw new IllegalArgumentException("The format of the device icon is unsupported."); + bitmap = Bitmap.createBitmap(context.getResources().getDisplayMetrics(), + ICON_TARGET_SIZE, ICON_TARGET_SIZE, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); } - return true; + + return Icon.createWithBitmap(bitmap); } } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 99794d7f49fe..252db824c69f 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -1116,11 +1116,8 @@ public final class VirtualDeviceManager { * @throws SecurityException if the display is not owned by this device or is not * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted} */ - @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { - if (Flags.vdmCustomIme()) { - mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy); - } + mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy); } /** diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 761e75bd9076..95dee9b72a88 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -440,7 +440,6 @@ public final class VirtualDeviceParams implements Parcelable { * * @see Builder#setInputMethodComponent */ - @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) @Nullable public ComponentName getInputMethodComponent() { return mInputMethodComponent; @@ -945,7 +944,6 @@ public final class VirtualDeviceParams implements Parcelable { * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker */ - @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) @NonNull public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) { mInputMethodComponent = inputMethodComponent; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index bbfae8117b16..7cd2d31ac974 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -62,6 +62,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Provides access to an application's raw asset files; see {@link Resources} @@ -133,7 +134,7 @@ public final class AssetManager implements AutoCloseable { // Debug/reference counting implementation. @GuardedBy("this") private boolean mOpen = true; - @GuardedBy("this") private int mNumRefs = 1; + private AtomicInteger mNumRefs = new AtomicInteger(1); @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; private ResourcesLoader[] mLoaders; @@ -244,7 +245,7 @@ public final class AssetManager implements AutoCloseable { mObject = nativeCreate(); if (DEBUG_REFS) { - mNumRefs = 0; + mNumRefs.set(0); incRefsLocked(hashCode()); } @@ -260,7 +261,7 @@ public final class AssetManager implements AutoCloseable { private AssetManager(boolean sentinel) { mObject = nativeCreate(); if (DEBUG_REFS) { - mNumRefs = 0; + mNumRefs.set(0); incRefsLocked(hashCode()); } } @@ -324,7 +325,7 @@ public final class AssetManager implements AutoCloseable { } mOpen = false; - decRefsLocked(hashCode()); + decRefs(hashCode()); } } @@ -1235,9 +1236,7 @@ public final class AssetManager implements AutoCloseable { } void xmlBlockGone(int id) { - synchronized (this) { - decRefsLocked(id); - } + decRefs(id); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1308,9 +1307,7 @@ public final class AssetManager implements AutoCloseable { } void releaseTheme(long themePtr) { - synchronized (this) { - decRefsLocked(themePtr); - } + decRefs(themePtr); } static long getThemeFreeFunction() { @@ -1332,7 +1329,7 @@ public final class AssetManager implements AutoCloseable { if (this != newAssetManager) { synchronized (this) { ensureValidLocked(); - decRefsLocked(themePtr); + decRefs(themePtr); } synchronized (newAssetManager) { newAssetManager.ensureValidLocked(); @@ -1364,8 +1361,8 @@ public final class AssetManager implements AutoCloseable { @Override protected void finalize() throws Throwable { - if (DEBUG_REFS && mNumRefs != 0) { - Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs); + if (DEBUG_REFS && mNumRefs.get() != 0) { + Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs.get()); if (mRefStacks != null) { for (RuntimeException e : mRefStacks.values()) { Log.w(TAG, "Reference from here", e); @@ -1473,9 +1470,7 @@ public final class AssetManager implements AutoCloseable { nativeAssetDestroy(mAssetNativePtr); mAssetNativePtr = 0; - synchronized (AssetManager.this) { - decRefsLocked(hashCode()); - } + decRefs(hashCode()); } } @@ -1680,19 +1675,25 @@ public final class AssetManager implements AutoCloseable { RuntimeException ex = new RuntimeException(); mRefStacks.put(id, ex); } - mNumRefs++; + mNumRefs.incrementAndGet(); } - @GuardedBy("this") - private void decRefsLocked(long id) { - if (DEBUG_REFS && mRefStacks != null) { - mRefStacks.remove(id); - } - mNumRefs--; - if (mNumRefs == 0 && mObject != 0) { - nativeDestroy(mObject); - mObject = 0; - mApkAssets = sEmptyApkAssets; + private void decRefs(long id) { + if (DEBUG_REFS) { + synchronized (this) { + if (mRefStacks != null) { + mRefStacks.remove(id); + } + } + } + if (mNumRefs.decrementAndGet() == 0) { + synchronized (this) { + if (mNumRefs.get() == 0 && mObject != 0) { + nativeDestroy(mObject); + mObject = 0; + mApkAssets = sEmptyApkAssets; + } + } } } diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 2161e10337c9..430ed2b68342 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -144,3 +144,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "credential_manager" + name: "fix_metric_duplication_emits" + description: "Fixes duplicate emits in the original metric emit system." + bug: "362994633" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 88d69b665c87..030c883924a7 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -182,17 +182,10 @@ public abstract class SQLiteOpenHelper implements AutoCloseable { setOpenParamsBuilder(openParamsBuilder); Object lock = null; - if (mName == null || !Flags.concurrentOpenHelper()) { + if (!Flags.concurrentOpenHelper() || mName == null) { lock = new Object(); } else { - try { - final String path = mContext.getDatabasePath(mName).getCanonicalPath(); - lock = sDbLock.computeIfAbsent(path, (String k) -> new Object()); - } catch (IOException e) { - Log.d(TAG, "failed to construct db path for " + mName); - // Ensure the lock is not null. - lock = new Object(); - } + lock = sDbLock.computeIfAbsent(mName, (String k) -> new Object()); } mLock = lock; } diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 4025242fd208..1a712d2b3f31 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -122,17 +122,11 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69; public static final int KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW = 70; public static final int KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW = 71; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN = 72; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT = 73; - public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74; - public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75; - public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76; - public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 77; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT = 78; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT = 79; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP = 80; - public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN = 81; - public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 82; + public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 72; + public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 73; + public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 74; + public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 75; + public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 76; public static final int FLAG_CANCELLED = 1; @@ -220,16 +214,10 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW, KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, - KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN, - KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT, KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK, KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP, - KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN, KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS, }) @Retention(RetentionPolicy.SOURCE) @@ -815,10 +803,6 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW: return "KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW"; - case KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN: - return "KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN"; - case KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT: - return "KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT"; case KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION: return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION"; case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK: @@ -827,14 +811,6 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB: return "KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP"; - case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN: - return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN"; case KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS: return "KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS"; default: diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 9181bd0cb2ed..953ee08800cf 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -771,6 +771,7 @@ public final class ContextHubManager { */ @FlaggedApi(Flags.FLAG_OFFLOAD_API) private IContextHubEndpointDiscoveryCallback createDiscoveryCallback( + IContextHubService service, Executor executor, HubEndpointDiscoveryCallback callback, @Nullable String serviceDescriptor) { @@ -779,6 +780,7 @@ public final class ContextHubManager { public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) { if (hubEndpointInfoList.length == 0) { Log.w(TAG, "onEndpointsStarted: received empty discovery list"); + invokeCallbackFinished(service); return; } executor.execute( @@ -791,6 +793,7 @@ public final class ContextHubManager { } else { callback.onEndpointsStarted(discoveryList); } + invokeCallbackFinished(service); }); } @@ -798,6 +801,7 @@ public final class ContextHubManager { public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) { if (hubEndpointInfoList.length == 0) { Log.w(TAG, "onEndpointsStopped: received empty discovery list"); + invokeCallbackFinished(service); return; } executor.execute( @@ -810,8 +814,17 @@ public final class ContextHubManager { } else { callback.onEndpointsStopped(discoveryList, reason); } + invokeCallbackFinished(service); }); } + + private void invokeCallbackFinished(IContextHubService service) { + try { + service.onDiscoveryCallbackFinished(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } }; } @@ -873,7 +886,7 @@ public final class ContextHubManager { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(callback, "callback cannot be null"); IContextHubEndpointDiscoveryCallback iCallback = - createDiscoveryCallback(executor, callback, null); + createDiscoveryCallback(mService, executor, callback, null); try { mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback); } catch (RemoteException e) { @@ -919,7 +932,7 @@ public final class ContextHubManager { } IContextHubEndpointDiscoveryCallback iCallback = - createDiscoveryCallback(executor, callback, serviceDescriptor); + createDiscoveryCallback(mService, executor, callback, serviceDescriptor); try { mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback); } catch (RemoteException e) { diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index d5b3fa251e82..bb5491d98cf9 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -150,4 +150,8 @@ interface IContextHubService { // Unregister an endpoint with the context hub @EnforcePermission("ACCESS_CONTEXT_HUB") void unregisterEndpointDiscoveryCallback(in IContextHubEndpointDiscoveryCallback callback); + + // Called when a discovery callback is finished executing + @EnforcePermission("ACCESS_CONTEXT_HUB") + void onDiscoveryCallbackFinished(); } diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java index 0541a96e990e..69b6597d717a 100644 --- a/core/java/android/os/AppZygote.java +++ b/core/java/android/os/AppZygote.java @@ -16,15 +16,22 @@ package android.os; +import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.pm.ProcessInfo; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.Zygote; import dalvik.system.VMRuntime; +import java.util.Map; + /** * AppZygote is responsible for interfacing with an application-specific zygote. * @@ -94,12 +101,90 @@ public class AppZygote { return mAppInfo; } + /** + * Start a new process. + * + * <p>Wrap ZygoteProcess.start with retry logic. + * + * @param processClass The class to use as the process's main entry + * point. + * @param niceName A more readable name to use for the process. + * @param uid The user-id under which the process will run. + * @param gids Additional group-ids associated with the process. + * @param runtimeFlags Additional flags. + * @param targetSdkVersion The target SDK version for the app. + * @param seInfo null-ok SELinux information for the new process. + * @param abi non-null the ABI this app should be started with. + * @param instructionSet null-ok the instruction set to use. + * @param appDataDir null-ok the data directory of the app. + * @param packageName null-ok the name of the package this process belongs to. + * @param isTopApp Whether the process starts for high priority application. + * @param disabledCompatChanges null-ok list of disabled compat changes for the process being + * started. + * @param pkgDataInfoMap Map from related package names to private data directory + * volume UUID and inode number. + * @param allowlistedDataInfoList Map from allowlisted package names to private data directory + * volume UUID and inode number. + * @param zygoteArgs Additional arguments to supply to the Zygote process. + * @return An object that describes the result of the attempt to start the process. + * @throws RuntimeException on fatal start failure + */ + public final Process.ProcessStartResult startProcess(@NonNull final String processClass, + final String niceName, + int uid, @Nullable int[] gids, + int runtimeFlags, int mountExternal, + int targetSdkVersion, + @Nullable String seInfo, + @NonNull String abi, + @Nullable String instructionSet, + @Nullable String appDataDir, + @Nullable String packageName, + boolean isTopApp, + @Nullable long[] disabledCompatChanges, + @Nullable Map<String, Pair<String, Long>> + pkgDataInfoMap, + @Nullable Map<String, Pair<String, Long>> + allowlistedDataInfoList, + @Nullable String[] zygoteArgs) { + try { + return getProcess().start(processClass, + niceName, uid, uid, gids, runtimeFlags, mountExternal, + targetSdkVersion, seInfo, abi, instructionSet, + appDataDir, null, packageName, + /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp, + disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList, + false, false, false, + zygoteArgs); + } catch (RuntimeException e) { + if (!Flags.appZygoteRetryStart()) { + throw e; + } + final boolean zygote_dead = getProcess().isDead(); + if (!zygote_dead) { + throw e; // Zygote process is alive. Do nothing. + } + } + // Retry here if the previous start fails. + Log.w(LOG_TAG, "retry starting process " + niceName); + stopZygote(); + return getProcess().start(processClass, + niceName, uid, uid, gids, runtimeFlags, mountExternal, + targetSdkVersion, seInfo, abi, instructionSet, + appDataDir, null, packageName, + /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp, + disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList, + false, false, false, + zygoteArgs); + } + @GuardedBy("mLock") private void stopZygoteLocked() { if (mZygote != null) { mZygote.close(); // use killProcessGroup() here, so we kill all untracked children as well. - Process.killProcessGroup(mZygoteUid, mZygote.getPid()); + if (!mZygote.isDead()) { + Process.killProcessGroup(mZygoteUid, mZygote.getPid()); + } mZygote = null; } } diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java index 337a3e279a1a..d8f825a2ee60 100644 --- a/core/java/android/os/ChildZygoteProcess.java +++ b/core/java/android/os/ChildZygoteProcess.java @@ -17,6 +17,10 @@ package android.os; import android.net.LocalSocketAddress; +import android.system.ErrnoException; +import android.system.Os; + +import java.util.concurrent.atomic.AtomicBoolean; /** * Represents a connection to a child-zygote process. A child-zygote is spawend from another @@ -30,9 +34,23 @@ public class ChildZygoteProcess extends ZygoteProcess { */ private final int mPid; - ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) { + /** + * The UID of the child zygote process. + */ + private final int mUid; + + + /** + * If this zygote process was dead; + */ + private AtomicBoolean mDead; + + + ChildZygoteProcess(LocalSocketAddress socketAddress, int pid, int uid) { super(socketAddress, null); mPid = pid; + mUid = uid; + mDead = new AtomicBoolean(false); } /** @@ -41,4 +59,22 @@ public class ChildZygoteProcess extends ZygoteProcess { public int getPid() { return mPid; } + + /** + * Check if child-zygote process is dead + */ + public boolean isDead() { + if (mDead.get()) { + return true; + } + try { + if (Os.stat("/proc/" + mPid).st_uid == mUid) { + return false; + } + } catch (ErrnoException e) { + // Do nothing, it's dead. + } + mDead.set(true); + return true; + } } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 4aa74621bd62..875b9098843f 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -2831,9 +2831,11 @@ public final class Parcel { } } - private void resetSqaushingState() { + private void resetSquashingState() { if (mAllowSquashing) { - Slog.wtf(TAG, "allowSquashing wasn't restored."); + String error = "allowSquashing wasn't restored."; + Slog.wtf(TAG, error); + throw new BadParcelableException(error); } mWrittenSquashableParcelables = null; mReadSquashableParcelables = null; @@ -2950,9 +2952,11 @@ public final class Parcel { for (int i = 0; i < mReadSquashableParcelables.size(); i++) { sb.append(mReadSquashableParcelables.keyAt(i)).append(' '); } - Slog.wtfStack(TAG, "Map doesn't contain offset " + String error = "Map doesn't contain offset " + firstAbsolutePos - + " : contains=" + sb.toString()); + + " : contains=" + sb.toString(); + Slog.wtfStack(TAG, error); + throw new BadParcelableException(error); } return (T) p; } @@ -5505,7 +5509,7 @@ public final class Parcel { private void freeBuffer() { mFlags = 0; - resetSqaushingState(); + resetSquashingState(); if (mOwnsNativeParcelObject) { nativeFreeBuffer(mNativePtr); } @@ -5513,7 +5517,7 @@ public final class Parcel { } private void destroy() { - resetSqaushingState(); + resetSquashingState(); if (mNativePtr != 0) { if (mOwnsNativeParcelObject) { nativeDestroy(mNativePtr); diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java index 164561acac32..e3f251e34b45 100644 --- a/core/java/android/os/PerfettoTrace.java +++ b/core/java/android/os/PerfettoTrace.java @@ -22,7 +22,6 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; /** * Writes trace events to the perfetto trace buffer. These trace events can be @@ -72,7 +71,7 @@ public final class PerfettoTrace { * @param name The category name. */ public Category(String name) { - this(name, null, null); + this(name, "", ""); } /** @@ -82,7 +81,7 @@ public final class PerfettoTrace { * @param tag An atrace tag name that this category maps to. */ public Category(String name, String tag) { - this(name, tag, null); + this(name, tag, ""); } /** @@ -155,9 +154,6 @@ public final class PerfettoTrace { } } - @FastNative - private static native void native_event(int type, long tag, String name, long ptr); - @CriticalNative private static native long native_get_process_track_uuid(); @@ -170,176 +166,98 @@ public final class PerfettoTrace { /** * Writes a trace message to indicate a given section of code was invoked. * - * @param category The perfetto category pointer. + * @param category The perfetto category. * @param eventName The event name to appear in the trace. - * @param extra The extra arguments. */ - public static void instant(Category category, String eventName, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_INSTANT, category.getPtr(), eventName, extra.getPtr()); - extra.reset(); - } - - /** - * Writes a trace message to indicate a given section of code was invoked. - * - * @param category The perfetto category. - * @param eventName The event name to appear in the trace. - * @param extraConfig Consumer for the extra arguments. - */ - public static void instant(Category category, String eventName, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - instant(category, eventName, extra.build()); - } - - /** - * Writes a trace message to indicate a given section of code was invoked. - * - * @param category The perfetto category. - * @param eventName The event name to appear in the trace. - */ - public static void instant(Category category, String eventName) { - instant(category, eventName, PerfettoTrackEventExtra.builder().build()); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category) + .setEventName(eventName); } /** * Writes a trace message to indicate the start of a given section of code. * - * @param category The perfetto category pointer. + * @param category The perfetto category. * @param eventName The event name to appear in the trace. - * @param extra The extra arguments. */ - public static void begin(Category category, String eventName, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_SLICE_BEGIN, category.getPtr(), eventName, extra.getPtr()); - extra.reset(); - } - - /** - * Writes a trace message to indicate the start of a given section of code. - * - * @param category The perfetto category pointer. - * @param eventName The event name to appear in the trace. - * @param extraConfig Consumer for the extra arguments. - */ - public static void begin(Category category, String eventName, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - begin(category, eventName, extra.build()); - } - - /** - * Writes a trace message to indicate the start of a given section of code. - * - * @param category The perfetto category pointer. - * @param eventName The event name to appear in the trace. - */ - public static void begin(Category category, String eventName) { - begin(category, eventName, PerfettoTrackEventExtra.builder().build()); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category) + .setEventName(eventName); } /** * Writes a trace message to indicate the end of a given section of code. * - * @param category The perfetto category pointer. - * @param extra The extra arguments. + * @param category The perfetto category. */ - public static void end(Category category, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder end(Category category) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_SLICE_END, category.getPtr(), "", extra.getPtr()); - extra.reset(); - } - - /** - * Writes a trace message to indicate the end of a given section of code. - * - * @param category The perfetto category pointer. - * @param extraConfig Consumer for the extra arguments. - */ - public static void end(Category category, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - end(category, extra.build()); - } - - /** - * Writes a trace message to indicate the end of a given section of code. - * - * @param category The perfetto category pointer. - */ - public static void end(Category category) { - end(category, PerfettoTrackEventExtra.builder().build()); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param extra The extra arguments. + * @param category The perfetto category. + * @param value The value of the counter. */ - public static void counter(Category category, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder counter(Category category, long value) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_COUNTER, category.getPtr(), "", extra.getPtr()); - extra.reset(); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category) + .setCounter(value); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param extraConfig Consumer for the extra arguments. + * @param category The perfetto category. + * @param value The value of the counter. + * @param trackName The trackName for the event. */ - public static void counter(Category category, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - counter(category, extra.build()); + public static PerfettoTrackEventExtra.Builder counter( + Category category, long value, String trackName) { + return counter(category, value).usingProcessCounterTrack(trackName); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param trackName The trackName for the event. + * @param category The perfetto category. * @param value The value of the counter. */ - public static void counter(Category category, String trackName, long value) { - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder() - .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid()) - .setCounter(value) - .build(); - counter(category, extra); + public static PerfettoTrackEventExtra.Builder counter(Category category, double value) { + if (!category.isEnabled()) { + return PerfettoTrackEventExtra.noOpBuilder(); + } + + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category) + .setCounter(value); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param trackName The trackName for the event. + * @param category The perfetto category. * @param value The value of the counter. + * @param trackName The trackName for the event. */ - public static void counter(Category category, String trackName, double value) { - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder() - .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid()) - .setCounter(value) - .build(); - counter(category, extra); + public static PerfettoTrackEventExtra.Builder counter( + Category category, double value, String trackName) { + return counter(category, value).usingProcessCounterTrack(trackName); } /** @@ -360,7 +278,7 @@ public final class PerfettoTrace { * Returns the process track uuid that can be used as a parent track uuid. */ public static long getProcessTrackUuid() { - if (IS_FLAG_ENABLED) { + if (!IS_FLAG_ENABLED) { return 0; } return native_get_process_track_uuid(); @@ -370,7 +288,7 @@ public final class PerfettoTrace { * Given a thread tid, returns the thread track uuid that can be used as a parent track uuid. */ public static long getThreadTrackUuid(long tid) { - if (IS_FLAG_ENABLED) { + if (!IS_FLAG_ENABLED) { return 0; } return native_get_thread_track_uuid(tid); @@ -380,7 +298,7 @@ public final class PerfettoTrace { * Activates a trigger by name {@code triggerName} with expiry in {@code ttlMs}. */ public static void activateTrigger(String triggerName, int ttlMs) { - if (IS_FLAG_ENABLED) { + if (!IS_FLAG_ENABLED) { return; } native_activate_trigger(triggerName, ttlMs); diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java index a219b3b5678b..e034fb3726e3 100644 --- a/core/java/android/os/PerfettoTrackEventExtra.java +++ b/core/java/android/os/PerfettoTrackEventExtra.java @@ -31,6 +31,7 @@ import java.util.function.Supplier; */ public final class PerfettoTrackEventExtra { private static final int DEFAULT_EXTRA_CACHE_SIZE = 5; + private static final Builder NO_OP_BUILDER = new NoOpBuilder(); private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra = new ThreadLocal<PerfettoTrackEventExtra>() { @Override @@ -40,7 +41,6 @@ public final class PerfettoTrackEventExtra { }; private static final AtomicLong sNamedTrackId = new AtomicLong(); - private boolean mIsInUse; private CounterInt64 mCounterInt64; private CounterDouble mCounterDouble; private Proto mProto; @@ -123,15 +123,299 @@ public final class PerfettoTrackEventExtra { } } + public interface Builder { + /** + * Emits the track event. + */ + void emit(); + + /** + * Initialize the builder for a new trace event. + */ + Builder init(int traceType, PerfettoTrace.Category category); + + /** + * Sets the event name for the track event. + * + * @param eventName can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code eventName} should be the string itself. + * @param args format arguments if {@code eventName} is specified. + */ + Builder setEventName(String eventName, Object... args); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ + Builder addArg(String name, long val); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ + Builder addArg(String name, boolean val); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ + Builder addArg(String name, double val); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + * + * @param val can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code val} should be the string itself. + * @param args format arguments if {@code val} is specified. + */ + Builder addArg(String name, String val, Object... args); + + /** + * Adds a flow with {@code id}. + */ + Builder addFlow(int id); + + /** + * Adds a terminating flow with {@code id}. + */ + Builder addTerminatingFlow(int id); + + /** + * Adds the events to a named track instead of the thread track where the + * event occurred. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingNamedTrack(long parentUuid, String name, Object... args); + + /** + * Adds the events to a process scoped named track instead of the thread track where the + * event occurred. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingProcessNamedTrack(String name, Object... args); + + /** + * Adds the events to a thread scoped named track instead of the thread track where the + * event occurred. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingThreadNamedTrack(long tid, String name, Object... args); + + /** + * Adds the events to a counter track instead. This is required for + * setting counter values. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingCounterTrack(long parentUuid, String name, Object... args); + + /** + * Adds the events to a process scoped counter track instead. This is required for + * setting counter values. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code eventName} is specified. + */ + Builder usingProcessCounterTrack(String name, Object... args); + + /** + * Adds the events to a thread scoped counter track instead. This is required for + * setting counter values. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingThreadCounterTrack(long tid, String name, Object... args); + + /** + * Sets a long counter value on the event. + * + */ + Builder setCounter(long val); + + /** + * Sets a double counter value on the event. + * + */ + Builder setCounter(double val); + + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ + Builder addField(long id, long val); + + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ + Builder addField(long id, double val); + + /** + * Adds a proto field with field id {@code id} and value {@code val}. + * + * @param val can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code val} should be the string itself. + * @param args format arguments if {@code val} is specified. + */ + Builder addField(long id, String val, Object... args); + + /** + * Begins a proto field with field + * Fields can be added from this point and there must be a corresponding + * {@link endProto}. + * + * The proto field is a singleton and all proto fields get added inside the + * one {@link beginProto} and {@link endProto} within the {@link Builder}. + */ + Builder beginProto(); + + /** + * Ends a proto field. + */ + Builder endProto(); + + /** + * Begins a nested proto field with field id {@code id}. + * Fields can be added from this point and there must be a corresponding + * {@link endNested}. + */ + Builder beginNested(long id); + + /** + * Ends a nested proto field. + */ + Builder endNested(); + } + + public static final class NoOpBuilder implements Builder { + @Override + public void emit() {} + @Override + public Builder init(int traceType, PerfettoTrace.Category category) { + return this; + } + @Override + public Builder setEventName(String eventName, Object... args) { + return this; + } + @Override + public Builder addArg(String name, long val) { + return this; + } + @Override + public Builder addArg(String name, boolean val) { + return this; + } + @Override + public Builder addArg(String name, double val) { + return this; + } + @Override + public Builder addArg(String name, String val, Object... args) { + return this; + } + @Override + public Builder addFlow(int id) { + return this; + } + @Override + public Builder addTerminatingFlow(int id) { + return this; + } + @Override + public Builder usingNamedTrack(long parentUuid, String name, Object... args) { + return this; + } + @Override + public Builder usingProcessNamedTrack(String name, Object... args) { + return this; + } + @Override + public Builder usingThreadNamedTrack(long tid, String name, Object... args) { + return this; + } + @Override + public Builder usingCounterTrack(long parentUuid, String name, Object... args) { + return this; + } + @Override + public Builder usingProcessCounterTrack(String name, Object... args) { + return this; + } + @Override + public Builder usingThreadCounterTrack(long tid, String name, Object... args) { + return this; + } + @Override + public Builder setCounter(long val) { + return this; + } + @Override + public Builder setCounter(double val) { + return this; + } + @Override + public Builder addField(long id, long val) { + return this; + } + @Override + public Builder addField(long id, double val) { + return this; + } + @Override + public Builder addField(long id, String val, Object... args) { + return this; + } + @Override + public Builder beginProto() { + return this; + } + @Override + public Builder endProto() { + return this; + } + @Override + public Builder beginNested(long id) { + return this; + } + @Override + public Builder endNested() { + return this; + } + } + /** * Builder for Perfetto track event extras. */ - public static final class Builder { + public static final class BuilderImpl implements Builder { // For performance reasons, we hold a reference to mExtra as a holder for // perfetto pointers being added. This way, we avoid an additional list to hold // the pointers in Java and we can pass them down directly to native code. private final PerfettoTrackEventExtra mExtra; + + private int mTraceType; + private PerfettoTrace.Category mCategory; + private String mEventName; private boolean mIsBuilt; + private Builder mParent; private FieldContainer mCurrentContainer; @@ -151,16 +435,10 @@ public final class PerfettoTrackEventExtra { private final Pool<FieldString> mFieldStringCache; private final Pool<FieldNested> mFieldNestedCache; private final Pool<Flow> mFlowCache; - private final Pool<Builder> mBuilderCache; + private final Pool<BuilderImpl> mBuilderCache; - private Builder() { - this(sTrackEventExtra.get(), null, null); - } - - private Builder(PerfettoTrackEventExtra extra, Builder parent, FieldContainer container) { - mExtra = extra; - mParent = parent; - mCurrentContainer = container; + private BuilderImpl() { + mExtra = sTrackEventExtra.get(); mNamedTrackCache = mExtra.mNamedTrackCache; mCounterTrackCache = mExtra.mCounterTrackCache; @@ -180,25 +458,39 @@ public final class PerfettoTrackEventExtra { mProto = mExtra.getProto(); } - /** - * Builds the track event extra. - */ - public PerfettoTrackEventExtra build() { + @Override + public void emit() { checkParent(); mIsBuilt = true; + native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr()); + // Reset after emitting to free any the extras used to trace the event. + mExtra.reset(); + } + + @Override + public Builder init(int traceType, PerfettoTrace.Category category) { + mTraceType = traceType; + mCategory = category; + mEventName = ""; mFieldInt64Cache.reset(); mFieldDoubleCache.reset(); mFieldStringCache.reset(); mFieldNestedCache.reset(); mBuilderCache.reset(); - return mExtra; + mExtra.reset(); + // Reset after on init in case the thread created builders without calling emit + return initInternal(this, null); } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ + @Override + public Builder setEventName(String eventName, Object... args) { + mEventName = toString(eventName, args); + return this; + } + + @Override public Builder addArg(String name, long val) { checkParent(); ArgInt64 arg = mArgInt64Cache.get(name.hashCode()); @@ -211,9 +503,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ + @Override public Builder addArg(String name, boolean val) { checkParent(); ArgBool arg = mArgBoolCache.get(name.hashCode()); @@ -226,9 +516,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ + @Override public Builder addArg(String name, double val) { checkParent(); ArgDouble arg = mArgDoubleCache.get(name.hashCode()); @@ -241,24 +529,20 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ - public Builder addArg(String name, String val) { + @Override + public Builder addArg(String name, String val, Object... args) { checkParent(); ArgString arg = mArgStringCache.get(name.hashCode()); if (arg == null || !arg.getName().equals(name)) { arg = new ArgString(name); mArgStringCache.put(name.hashCode(), arg); } - arg.setValue(val); + arg.setValue(toString(val, args)); mExtra.addPerfettoPointer(arg); return this; } - /** - * Adds a flow with {@code id}. - */ + @Override public Builder addFlow(int id) { checkParent(); Flow flow = mFlowCache.get(Flow::new); @@ -267,9 +551,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a terminating flow with {@code id}. - */ + @Override public Builder addTerminatingFlow(int id) { checkParent(); Flow flow = mFlowCache.get(Flow::new); @@ -278,12 +560,11 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds the events to a named track instead of the thread track where the - * event occurred. - */ - public Builder usingNamedTrack(String name, long parentUuid) { + @Override + public Builder usingNamedTrack(long parentUuid, String name, Object... args) { checkParent(); + name = toString(name, args); + NamedTrack track = mNamedTrackCache.get(name.hashCode()); if (track == null || !track.getName().equals(name)) { track = new NamedTrack(name, parentUuid); @@ -293,13 +574,21 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds the events to a counter track instead. This is required for - * setting counter values. - * - */ - public Builder usingCounterTrack(String name, long parentUuid) { + @Override + public Builder usingProcessNamedTrack(String name, Object... args) { + return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name, args); + } + + @Override + public Builder usingThreadNamedTrack(long tid, String name, Object... args) { + return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args); + } + + @Override + public Builder usingCounterTrack(long parentUuid, String name, Object... args) { checkParent(); + name = toString(name, args); + CounterTrack track = mCounterTrackCache.get(name.hashCode()); if (track == null || !track.getName().equals(name)) { track = new CounterTrack(name, parentUuid); @@ -309,10 +598,17 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Sets a long counter value on the event. - * - */ + @Override + public Builder usingProcessCounterTrack(String name, Object... args) { + return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name, args); + } + + @Override + public Builder usingThreadCounterTrack(long tid, String name, Object... args) { + return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args); + } + + @Override public Builder setCounter(long val) { checkParent(); mCounterInt64.setValue(val); @@ -320,10 +616,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Sets a double counter value on the event. - * - */ + @Override public Builder setCounter(double val) { checkParent(); mCounterDouble.setValue(val); @@ -331,9 +624,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ + @Override public Builder addField(long id, long val) { checkContainer(); FieldInt64 field = mFieldInt64Cache.get(FieldInt64::new); @@ -342,9 +633,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ + @Override public Builder addField(long id, double val) { checkContainer(); FieldDouble field = mFieldDoubleCache.get(FieldDouble::new); @@ -353,35 +642,24 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ - public Builder addField(long id, String val) { + @Override + public Builder addField(long id, String val, Object... args) { checkContainer(); FieldString field = mFieldStringCache.get(FieldString::new); - field.setValue(id, val); + field.setValue(id, toString(val, args)); mCurrentContainer.addField(field); return this; } - /** - * Begins a proto field with field - * Fields can be added from this point and there must be a corresponding - * {@link endProto}. - * - * The proto field is a singleton and all proto fields get added inside the - * one {@link beginProto} and {@link endProto} within the {@link Builder}. - */ + @Override public Builder beginProto() { checkParent(); mProto.clearFields(); mExtra.addPerfettoPointer(mProto); - return mBuilderCache.get(Builder::new).init(this, mProto); + return mBuilderCache.get(BuilderImpl::new).initInternal(this, mProto); } - /** - * Ends a proto field. - */ + @Override public Builder endProto() { if (mParent == null || mCurrentContainer == null) { throw new IllegalStateException("No proto to end"); @@ -389,22 +667,16 @@ public final class PerfettoTrackEventExtra { return mParent; } - /** - * Begins a nested proto field with field id {@code id}. - * Fields can be added from this point and there must be a corresponding - * {@link endNested}. - */ + @Override public Builder beginNested(long id) { checkContainer(); FieldNested field = mFieldNestedCache.get(FieldNested::new); field.setId(id); mCurrentContainer.addField(field); - return mBuilderCache.get(Builder::new).init(this, field); + return mBuilderCache.get(BuilderImpl::new).initInternal(this, field); } - /** - * Ends a nested proto field. - */ + @Override public Builder endNested() { if (mParent == null || mCurrentContainer == null) { throw new IllegalStateException("No nested field to end"); @@ -412,21 +684,15 @@ public final class PerfettoTrackEventExtra { return mParent; } - /** - * Initializes a {@link Builder}. - */ - public Builder init(Builder parent, FieldContainer container) { + private static String toString(String val, Object... args) { + return args == null || args.length == 0 ? val : String.format(val, args); + } + + private Builder initInternal(Builder parent, FieldContainer container) { mParent = parent; mCurrentContainer = container; mIsBuilt = false; - if (mParent == null) { - if (mExtra.mIsInUse) { - throw new IllegalStateException("Cannot create a new builder when another" - + " extra is in use"); - } - mExtra.mIsInUse = true; - } return this; } @@ -439,9 +705,8 @@ public final class PerfettoTrackEventExtra { private void checkParent() { checkState(); - if (mParent != null) { - throw new IllegalStateException( - "This builder has already been used. Create a new builder for another event."); + if (!this.equals(mParent)) { + throw new IllegalStateException("Operation not supported for proto"); } } @@ -458,7 +723,14 @@ public final class PerfettoTrackEventExtra { * Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}. */ public static Builder builder() { - return sTrackEventExtra.get().mBuilderCache.get(Builder::new).init(null, null); + return sTrackEventExtra.get().mBuilderCache.get(BuilderImpl::new).initInternal(null, null); + } + + /** + * Returns a no-op {@link Builder}. Useful if a category is disabled. + */ + public static Builder noOpBuilder() { + return NO_OP_BUILDER; } private final RingBuffer<NamedTrack> mNamedTrackCache = @@ -476,7 +748,7 @@ public final class PerfettoTrackEventExtra { private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); - private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); + private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( @@ -509,7 +781,6 @@ public final class PerfettoTrackEventExtra { */ public void reset() { native_clear_args(mPtr); - mIsInUse = false; } private CounterInt64 getCounterInt64() { @@ -1078,4 +1349,6 @@ public final class PerfettoTrackEventExtra { private static native void native_add_arg(long ptr, long extraPtr); @CriticalNative private static native void native_clear_args(long ptr); + @FastNative + private static native void native_emit(int type, long tag, String name, long ptr); } diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 9435f4d59a2c..77b6d70339ab 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -351,4 +351,10 @@ public abstract class PowerManagerInternal { * return false if ambient display is not available. */ public abstract boolean isAmbientDisplaySuppressed(); + + /** + * Notifies PowerManager that the device has entered a postured state (stationary + upright). + * This may affect dream eligibility. + */ + public abstract void setDevicePostured(boolean isPostured); } diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 73a74b2f8abc..2d487b1e77d5 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -1319,6 +1319,6 @@ public class ZygoteProcess { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } - return new ChildZygoteProcess(serverAddress, result.pid); + return new ChildZygoteProcess(serverAddress, result.pid, uid); } } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 8b8369890d1b..b12433a73a31 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -150,6 +150,13 @@ flag { } flag { + name: "app_zygote_retry_start" + namespace: "arc_next" + description: "Guard the new added retry logic in app zygote." + bug: "361799815" +} + +flag { name: "battery_part_status_api" is_exported: true namespace: "phoenix" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0cfec2cc7314..a22333bd1812 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9314,6 +9314,16 @@ public final class Settings { "accessibility_autoclick_cursor_area_size"; /** + * Setting that specifies whether minor cursor movement will be ignored when + * {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set. + * + * @see #ACCESSIBILITY_AUTOCLICK_ENABLED + * @hide + */ + public static final String ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT = + "accessibility_autoclick_ignore_minor_cursor_movement"; + + /** * Whether or not larger size icons are used for the pointer of mouse/trackpad for * accessibility. * (0 = false, 1 = true) @@ -12869,6 +12879,19 @@ public final class Settings { */ public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows"; + /** + * Controls if the adaptive authentication feature should be disabled, which + * will attempt to lock the device after a number of consecutive authentication + * attempts fail. + * + * This can only be disabled on debuggable builds. Set to 1 to disable or 0 for the + * normal behavior. + * + * @hide + */ + public static final String DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK = + "disable_adaptive_auth_limit_lock"; + /** @hide */ public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0; /** @hide */ diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 4a9e945e62a9..792e6ff52d01 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -43,7 +43,7 @@ flag { flag { name: "secure_array_zeroization" - namespace: "platform_security" + namespace: "security" description: "Enable secure array zeroization" bug: "320392352" metadata { @@ -155,4 +155,11 @@ flag { description: "Feature flag to add the privileged flag to the SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE permission" bug: "380120712" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + name: "disable_adaptive_auth_counter_lock" + namespace: "biometrics" + description: "Flag to allow an adb secure setting to disable the adaptive auth lock" + bug: "371057865" +} diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 1c0a2c021bca..3ca9d937b0ca 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -53,6 +53,8 @@ interface IDreamManager { void startDreamActivity(in Intent intent); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") oneway void setDreamIsObscured(in boolean isObscured); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") + oneway void setDevicePostured(in boolean isPostured); oneway void startDozingOneway(in IBinder token, int screenState, int reason, float screenBrightnessFloat, int screenBrightnessInt, boolean useNormalBrightnessForDoze); diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java index b5251db4e539..051885e10132 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java @@ -25,6 +25,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import java.io.Closeable; import java.util.concurrent.Executor; @@ -232,6 +233,15 @@ public interface QuickAccessWalletClient extends Closeable { Drawable getTileIcon(); /** + * Returns the user that should receive the wallet intents + * + * @return UserHandle + * @hide + */ + @Nullable + UserHandle getUser(); + + /** * Returns the service label specified by {@code android:label} in the service manifest entry. * * @hide diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java index 97a4beff633f..177164296eea 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java @@ -243,8 +243,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return null; } String packageName = mServiceInfo.getComponentName().getPackageName(); + int userId = mServiceInfo.getUserId(); String walletActivity = mServiceInfo.getWalletActivity(); - return createIntent(walletActivity, packageName, ACTION_VIEW_WALLET); + return createIntent(walletActivity, packageName, userId, ACTION_VIEW_WALLET); } @Override @@ -302,12 +303,15 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser } String packageName = mServiceInfo.getComponentName().getPackageName(); String settingsActivity = mServiceInfo.getSettingsActivity(); - return createIntent(settingsActivity, packageName, ACTION_VIEW_WALLET_SETTINGS); + return createIntent(settingsActivity, packageName, UserHandle.myUserId(), + ACTION_VIEW_WALLET_SETTINGS); } @Nullable - private Intent createIntent(@Nullable String activityName, String packageName, String action) { - PackageManager pm = mContext.getPackageManager(); + private Intent createIntent(@Nullable String activityName, String packageName, + int userId, String action) { + Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0); + PackageManager pm = userContext.getPackageManager(); if (TextUtils.isEmpty(activityName)) { activityName = queryActivityForAction(pm, packageName, action); } @@ -361,6 +365,12 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return mServiceInfo == null ? null : mServiceInfo.getTileIcon(); } + @Nullable + @Override + public UserHandle getUser() { + return mServiceInfo == null ? null : UserHandle.of(mServiceInfo.getUserId()); + } + @Override @Nullable public CharSequence getServiceLabel() { diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java index 8a3f6ceb852b..01de54354a04 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java @@ -16,6 +16,10 @@ package android.service.quickaccesswallet; +import static android.permission.flags.Flags.walletRoleCrossUserEnabled; + +import static com.android.permission.flags.Flags.crossUserRoleEnabled; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,10 +36,12 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.Binder; +import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.util.Xml; import com.android.internal.R; @@ -59,22 +65,29 @@ class QuickAccessWalletServiceInfo { private final ServiceInfo mServiceInfo; private final ServiceMetadata mServiceMetadata; private final TileServiceMetadata mTileServiceMetadata; + private final int mUserId; private QuickAccessWalletServiceInfo( @NonNull ServiceInfo serviceInfo, @NonNull ServiceMetadata metadata, - @NonNull TileServiceMetadata tileServiceMetadata) { + @NonNull TileServiceMetadata tileServiceMetadata, + int userId) { mServiceInfo = serviceInfo; mServiceMetadata = metadata; mTileServiceMetadata = tileServiceMetadata; + mUserId = userId; } @Nullable static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) { String defaultAppPackageName = null; + int defaultAppUser = UserHandle.myUserId(); + if (isWalletRoleAvailable(context)) { - defaultAppPackageName = getDefaultWalletApp(context); + Pair<String, Integer> roleAndUser = getDefaultWalletApp(context); + defaultAppPackageName = roleAndUser.first; + defaultAppUser = roleAndUser.second; } else { ComponentName defaultPaymentApp = getDefaultPaymentApp(context); if (defaultPaymentApp == null) { @@ -83,7 +96,8 @@ class QuickAccessWalletServiceInfo { defaultAppPackageName = defaultPaymentApp.getPackageName(); } - ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName); + ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName, + defaultAppUser); if (serviceInfo == null) { return null; } @@ -98,15 +112,32 @@ class QuickAccessWalletServiceInfo { ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo); TileServiceMetadata tileServiceMetadata = new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo)); - return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata); + return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata, + defaultAppUser); } - private static String getDefaultWalletApp(Context context) { + @NonNull + private static Pair<String, Integer> getDefaultWalletApp(Context context) { + UserHandle user = UserHandle.of(UserHandle.myUserId()); + final long token = Binder.clearCallingIdentity(); try { RoleManager roleManager = context.getSystemService(RoleManager.class); - List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET); - return roleHolders.isEmpty() ? null : roleHolders.get(0); + + if (walletRoleCrossUserEnabled() + && crossUserRoleEnabled() + && context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET); + if (user == null) { + return new Pair<>(null, user.getIdentifier()); + } + } + List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET, + user); + return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0), + user.getIdentifier()); } finally { Binder.restoreCallingIdentity(token); } @@ -128,15 +159,16 @@ class QuickAccessWalletServiceInfo { return comp == null ? null : ComponentName.unflattenFromString(comp); } - private static ServiceInfo getWalletServiceInfo(Context context, String packageName) { + private static ServiceInfo getWalletServiceInfo(Context context, String packageName, + int userId) { Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE); intent.setPackage(packageName); List<ResolveInfo> resolveInfos = - context.getPackageManager().queryIntentServices(intent, + context.getPackageManager().queryIntentServicesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DEFAULT_ONLY - | PackageManager.GET_META_DATA); + | PackageManager.GET_META_DATA, userId); return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo; } @@ -247,6 +279,9 @@ class QuickAccessWalletServiceInfo { return mServiceInfo.getComponentName(); } + int getUserId() { + return mUserId; + } /** * @return the fully qualified name of the activity that hosts the full wallet. If available, * this intent should be started with the action diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index eeb56d4d6c05..464756842caf 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2424,9 +2424,10 @@ public abstract class WallpaperService extends Service { Surface ret = null; if (mBlastBufferQueue == null) { - mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl, - width, height, format); + mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", + true /* updateDestinationFrame */); mBlastBufferQueue.setApplyToken(mBbqApplyToken); + mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java index c69ddf86d3f8..fb4a19b9dcac 100644 --- a/core/java/android/timezone/TelephonyNetworkFinder.java +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -19,6 +19,9 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.i18n.timezone.MobileCountries; +import com.android.icu.Flags; + import java.util.Objects; /** @@ -50,4 +53,17 @@ public final class TelephonyNetworkFinder { return telephonyNetworkDelegate != null ? new TelephonyNetwork(telephonyNetworkDelegate) : null; } + + /** + * Returns the countries where a given MCC is in use. + */ + @Nullable + public MobileCountries findCountriesByMcc(@NonNull String mcc) { + Objects.requireNonNull(mcc); + + if (!Flags.telephonyLookupMccExtension()) { + return null; + } + return mDelegate.findCountriesByMcc(mcc); + } } diff --git a/core/java/android/util/proto/ProtoFieldFilter.java b/core/java/android/util/proto/ProtoFieldFilter.java new file mode 100644 index 000000000000..9c4144403237 --- /dev/null +++ b/core/java/android/util/proto/ProtoFieldFilter.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2025 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.util.proto; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.function.Predicate; + +/** + * A utility class that reads raw protobuf data from an InputStream + * and copies only those fields for which a given predicate returns true. + * + * <p> + * This is a low-level approach that does not fully decode fields + * (unless necessary to determine lengths). It simply: + * <ul> + * <li>Parses each field's tag (varint for field number & wire type)</li> + * <li>If {@code includeFn(fieldNumber) == true}, copies + * the tag bytes and the field bytes directly to the output</li> + * <li>Otherwise, skips that field in the input</li> + * </ul> + * </p> + * + * <p> + * Because we do not re-encode, unknown or unrecognized fields are copied + * <i>verbatim</i> and remain exactly as in the input (useful for partial + * parsing or partial transformations). + * </p> + * + * <p> + * Note: This class only filters based on top-level field numbers. For length-delimited + * fields (including nested messages), the entire contents are either copied or skipped + * as a single unit. The class is not capable of nested filtering. + * </p> + * + * @hide + */ +public class ProtoFieldFilter { + + private static final int BUFFER_SIZE_BYTES = 4096; + + private final Predicate<Integer> mFieldPredicate; + // General-purpose buffer for reading proto fields and their data + private final byte[] mBuffer; + // Buffer specifically designated to hold varint values (max 10 bytes in protobuf encoding) + private final byte[] mVarIntBuffer = new byte[10]; + + /** + * Constructs a ProtoFieldFilter with a predicate that considers depth. + * + * @param fieldPredicate A predicate returning true if the given fieldNumber should be + * included in the output. + * @param bufferSize The size of the internal buffer used for processing proto fields. + * Larger buffers may improve performance when processing large + * length-delimited fields. + */ + public ProtoFieldFilter(Predicate<Integer> fieldPredicate, int bufferSize) { + this.mFieldPredicate = fieldPredicate; + this.mBuffer = new byte[bufferSize]; + } + + /** + * Constructs a ProtoFieldFilter with a predicate that considers depth and + * uses a default buffer size. + * + * @param fieldPredicate A predicate returning true if the given fieldNumber should be + * included in the output. + */ + public ProtoFieldFilter(Predicate<Integer> fieldPredicate) { + this(fieldPredicate, BUFFER_SIZE_BYTES); + } + + /** + * Reads raw protobuf data from {@code in} and writes only those fields + * passing {@code includeFn} to {@code out}. The predicate is given + * (fieldNumber, wireType) for each encountered field. + * + * @param in The input stream of protobuf data + * @param out The output stream to which we write the filtered protobuf + * @throws IOException If reading or writing fails, or if the protobuf data is corrupted + */ + public void filter(InputStream in, OutputStream out) throws IOException { + int tagBytesLength; + while ((tagBytesLength = readRawVarint(in)) > 0) { + // Parse the varint loaded in mVarIntBuffer, through readRawVarint + long tagVal = parseVarint(mVarIntBuffer, tagBytesLength); + int fieldNumber = (int) (tagVal >>> ProtoStream.FIELD_ID_SHIFT); + int wireType = (int) (tagVal & ProtoStream.WIRE_TYPE_MASK); + + if (fieldNumber == 0) { + break; + } + if (mFieldPredicate.test(fieldNumber)) { + out.write(mVarIntBuffer, 0, tagBytesLength); + copyFieldData(in, out, wireType); + } else { + skipFieldData(in, wireType); + } + } + } + + /** + * Reads a varint (up to 10 bytes) from the stream as raw bytes + * and returns it in a byte array. If the stream is at EOF, returns null. + * + * @param in The input stream + * @return the size of the varint bytes moved to mVarIntBuffer + * @throws IOException If an error occurs, or if we detect a malformed varint + */ + private int readRawVarint(InputStream in) throws IOException { + // We attempt to read 1 byte. If none available => null + int b = in.read(); + if (b < 0) { + return 0; + } + int count = 0; + mVarIntBuffer[count++] = (byte) b; + // If the continuation bit is set, we continue + while ((b & 0x80) != 0) { + // read next byte + b = in.read(); + // EOF + if (b < 0) { + throw new IOException("Malformed varint: reached EOF mid-varint"); + } + // max 10 bytes for varint 64 + if (count >= 10) { + throw new IOException("Malformed varint: too many bytes (max 10)"); + } + mVarIntBuffer[count++] = (byte) b; + } + return count; + } + + /** + * Parses a varint from the given raw bytes and returns it as a long. + * + * @param rawVarint The bytes representing the varint + * @param byteLength The number of bytes to read from rawVarint + * @return The decoded long value + */ + private static long parseVarint(byte[] rawVarint, int byteLength) throws IOException { + long result = 0; + int shift = 0; + for (int i = 0; i < byteLength; i++) { + result |= ((rawVarint[i] & 0x7F) << shift); + shift += 7; + if (shift > 63) { + throw new IOException("Malformed varint: exceeds 64 bits"); + } + } + return result; + } + + /** + * Copies the wire data for a single field from {@code in} to {@code out}, + * assuming we have already read the field's tag. + * + * @param in The input stream (protobuf data) + * @param out The output stream + * @param wireType The wire type (0=varint, 1=fixed64, 2=length-delim, 5=fixed32) + * @throws IOException if reading/writing fails or data is malformed + */ + private void copyFieldData(InputStream in, OutputStream out, int wireType) + throws IOException { + switch (wireType) { + case ProtoStream.WIRE_TYPE_VARINT: + copyVarint(in, out); + break; + case ProtoStream.WIRE_TYPE_FIXED64: + copyFixed(in, out, 8); + break; + case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED: + copyLengthDelimited(in, out); + break; + case ProtoStream.WIRE_TYPE_FIXED32: + copyFixed(in, out, 4); + break; + // case WIRE_TYPE_START_GROUP: + // Not Supported + // case WIRE_TYPE_END_GROUP: + // Not Supported + default: + // Error or unrecognized wire type + throw new IOException("Unknown or unsupported wire type: " + wireType); + } + } + + /** + * Skips the wire data for a single field from {@code in}, + * assuming the field's tag was already read. + */ + private void skipFieldData(InputStream in, int wireType) throws IOException { + switch (wireType) { + case ProtoStream.WIRE_TYPE_VARINT: + skipVarint(in); + break; + case ProtoStream.WIRE_TYPE_FIXED64: + skipBytes(in, 8); + break; + case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED: + skipLengthDelimited(in); + break; + case ProtoStream.WIRE_TYPE_FIXED32: + skipBytes(in, 4); + break; + // case WIRE_TYPE_START_GROUP: + // Not Supported + // case WIRE_TYPE_END_GROUP: + // Not Supported + default: + throw new IOException("Unknown or unsupported wire type: " + wireType); + } + } + + /** Copies a varint (the field's value) from in to out. */ + private static void copyVarint(InputStream in, OutputStream out) throws IOException { + while (true) { + int b = in.read(); + if (b < 0) { + throw new IOException("EOF while copying varint"); + } + out.write(b); + if ((b & 0x80) == 0) { + break; + } + } + } + + /** + * Copies exactly {@code length} bytes from {@code in} to {@code out}. + */ + private void copyFixed(InputStream in, OutputStream out, + int length) throws IOException { + int toRead = length; + while (toRead > 0) { + int chunk = Math.min(toRead, mBuffer.length); + int readCount = in.read(mBuffer, 0, chunk); + if (readCount < 0) { + throw new IOException("EOF while copying fixed" + (length * 8) + " field"); + } + out.write(mBuffer, 0, readCount); + toRead -= readCount; + } + } + + /** Copies a length-delimited field */ + private void copyLengthDelimited(InputStream in, + OutputStream out) throws IOException { + // 1) read length varint (and copy) + int lengthVarintLength = readRawVarint(in); + if (lengthVarintLength <= 0) { + throw new IOException("EOF reading length for length-delimited field"); + } + out.write(mVarIntBuffer, 0, lengthVarintLength); + + long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength); + if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) { + throw new IOException("Invalid length for length-delimited field: " + lengthVal); + } + + // 2) copy that many bytes + copyFixed(in, out, (int) lengthVal); + } + + /** Skips a varint in the input (does not write anything). */ + private static void skipVarint(InputStream in) throws IOException { + int bytesSkipped = 0; + while (true) { + int b = in.read(); + if (b < 0) { + throw new IOException("EOF while skipping varint"); + } + if ((b & 0x80) == 0) { + break; + } + bytesSkipped++; + if (bytesSkipped > 10) { + throw new IOException("Malformed varint: exceeds maximum length of 10 bytes"); + } + } + } + + /** Skips exactly n bytes. */ + private void skipBytes(InputStream in, long n) throws IOException { + long skipped = in.skip(n); + // If skip fails, fallback to reading the remaining bytes + if (skipped < n) { + long bytesRemaining = n - skipped; + + while (bytesRemaining > 0) { + int bytesToRead = (int) Math.min(bytesRemaining, mBuffer.length); + int bytesRead = in.read(mBuffer, 0, bytesToRead); + if (bytesRemaining < 0) { + throw new IOException("EOF while skipping bytes"); + } + bytesRemaining -= bytesRead; + } + } + } + + /** + * Skips a length-delimited field. + * 1) read the length as varint, + * 2) skip that many bytes + */ + private void skipLengthDelimited(InputStream in) throws IOException { + int lengthVarintLength = readRawVarint(in); + if (lengthVarintLength <= 0) { + throw new IOException("EOF reading length for length-delimited field"); + } + long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength); + if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) { + throw new IOException("Invalid length to skip: " + lengthVal); + } + skipBytes(in, lengthVal); + } + +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 028043340732..06eb0428bfcf 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -227,9 +227,6 @@ interface IWindowManager @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void endProlongedAnimations(); - void startFreezingScreen(int exitAnim, int enterAnim); - void stopFreezingScreen(); - // these require DISABLE_KEYGUARD permission /** @deprecated use Activity.setShowWhenLocked instead. */ void disableKeyguard(IBinder token, String tag, int userId); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index d7cf3e827695..311fbee2fc4b 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3026,6 +3026,7 @@ public final class SurfaceControl implements Parcelable { // Only non-null if the SurfaceControlRegistry is enabled. This list tracks the set of calls // made through this transaction object, and is dumped (and cleared) when the transaction is // later applied. + @Nullable ArrayList<String> mCalls; Runnable mFreeNativeResources; @@ -4898,8 +4899,10 @@ public final class SurfaceControl implements Parcelable { SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( "merge", this, null, "otherTx=" + other.getId()); if (mCalls != null) { - mCalls.addAll(other.mCalls); - other.mCalls.clear(); + if (other.mCalls != null) { + mCalls.addAll(other.mCalls); + other.mCalls.clear(); + } } } mResizedSurfaces.putAll(other.mResizedSurfaces); diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 121c01be7294..0b528bffe5c5 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -334,13 +334,17 @@ public class SurfaceControlRegistry { if (call == APPLY) { // Log the apply and dump the calls on that transaction Log.e(TAG, msg, new Throwable()); - for (int i = 0; i < tx.mCalls.size(); i++) { - Log.d(TAG, " " + tx.mCalls.get(i)); + if (tx.mCalls != null) { + for (int i = 0; i < tx.mCalls.size(); i++) { + Log.d(TAG, " " + tx.mCalls.get(i)); + } } } else if (matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) { // Otherwise log this call to the transaction if it matches the tracked calls Log.e(TAG, msg, new Throwable()); - tx.mCalls.add(msg); + if (tx.mCalls != null) { + tx.mCalls.add(msg); + } } } else { // Log this call if it matches the tracked calls diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a657acd261d4..64e7becb1ed4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2759,14 +2759,15 @@ public final class ViewRootImpl implements ViewParent, if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); } - mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, - mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); - mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); - mBlastBufferQueue.setWaitForBufferReleaseCallback(mChoreographer::onWaitForBufferRelease); + mBlastBufferQueue = new BLASTBufferQueue(mTag, true /* updateDestinationFrame */); // If we create and destroy BBQ without recreating the SurfaceControl, we can end up // queuing buffers on multiple apply tokens causing out of order buffer submissions. We // fix this by setting the same apply token on all BBQs created by this VRI. mBlastBufferQueue.setApplyToken(mBbqApplyToken); + mBlastBufferQueue.update(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, + mWindowAttributes.format); + mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); + mBlastBufferQueue.setWaitForBufferReleaseCallback(mChoreographer::onWaitForBufferRelease); Surface blastSurface; if (addSchandleToVriSurface()) { blastSurface = mBlastBufferQueue.createSurfaceWithHandle(); @@ -5551,6 +5552,9 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mContentCaptureManager != null) { ContentCaptureSession session = mAttachInfo.mContentCaptureManager.getMainContentCaptureSession(); + if (android.view.contentcapture.flags.Flags.postCreateAndroidBgThread()) { + session.performStart(); + } session.notifyWindowBoundsChanged(session.getId(), getConfiguration().windowConfiguration.getBounds()); } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 64277b14098d..d267c9451d1a 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -160,6 +160,9 @@ public final class AccessibilityManager { /** @hide */ public static final int AUTOCLICK_CURSOR_AREA_INCREMENT_SIZE = 20; + /** @hide */ + public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false; + /** * Activity action: Launch UI to manage which accessibility service or feature is assigned * to the navigation bar Accessibility button. diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index 8baa55f8e377..6e2e1009fd40 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -74,8 +74,8 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override - void flush(@FlushReason int reason) { - mParent.flush(reason); + void internalFlush(@FlushReason int reason) { + mParent.internalFlush(reason); } @Override diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 724e8fa830af..b7a77d701045 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -52,7 +52,6 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.RingBuffer; import com.android.internal.util.SyncResultReceiver; @@ -605,7 +604,6 @@ public final class ContentCaptureManager { mContext, this, prepareUiHandler(), - prepareContentCaptureHandler(), mService ); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); @@ -616,15 +614,6 @@ public final class ContentCaptureManager { @NonNull @GuardedBy("mLock") - private Handler prepareContentCaptureHandler() { - if (mContentCaptureHandler == null) { - mContentCaptureHandler = BackgroundThread.getHandler(); - } - return mContentCaptureHandler; - } - - @NonNull - @GuardedBy("mLock") private Handler prepareUiHandler() { if (mUiHandler == null) { mUiHandler = Handler.createAsync(Looper.getMainLooper()); @@ -674,7 +663,7 @@ public final class ContentCaptureManager { @UiThread public void flush(@FlushReason int reason) { if (mOptions.lite) return; - getMainContentCaptureSession().flush(reason); + getMainContentCaptureSession().internalFlush(reason); } /** diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 9aeec20ec9b7..791a6f4254ec 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -286,6 +286,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags); + /** @hide */ + public void performStart() {} + abstract boolean isDisabled(); /** @@ -339,7 +342,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** * Flushes the buffered events to the service. */ - abstract void flush(@FlushReason int reason); + abstract void internalFlush(@FlushReason int reason); /** * Sets the {@link ContentCaptureContext} associated with the session. diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 2fb78c038ca2..29cae857098d 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -57,10 +57,12 @@ import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import android.view.contentcapture.flags.Flags; import android.view.contentprotection.ContentProtectionEventProcessor; import android.view.inputmethod.BaseInputConnection; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.os.IResultReceiver; import com.android.modules.expresslog.Counter; @@ -107,8 +109,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @NonNull private final Handler mUiHandler; - @NonNull - private final Handler mContentCaptureHandler; + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @Nullable + public Handler mContentCaptureHandler; /** * Interface to the system_server binder object - it's only used to start the session (and @@ -187,6 +191,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Nullable public ContentProtectionEventProcessor mContentProtectionEventProcessor; + /** + * A runnable object to perform the start of this session. + */ + @Nullable + private Runnable mStartRunnable = null; + private static class SessionStateReceiver extends IResultReceiver.Stub { private final WeakReference<MainContentCaptureSession> mMainSession; @@ -198,7 +208,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { public void send(int resultCode, Bundle resultData) { final MainContentCaptureSession mainSession = mMainSession.get(); if (mainSession == null) { - Log.w(TAG, "received result after mina session released"); + Log.w(TAG, "received result after main session released"); return; } final IBinder binder; @@ -213,6 +223,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { binder = resultData.getBinder(EXTRA_BINDER); if (binder == null) { Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); + // explicitly init the bg thread + mainSession.mContentCaptureHandler = mainSession.prepareContentCaptureHandler(); mainSession.runOnContentCaptureThread(() -> mainSession.resetSession( STATE_DISABLED | STATE_INTERNAL_ERROR)); return; @@ -220,23 +232,45 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } else { binder = null; } + // explicitly init the bg thread + mainSession.mContentCaptureHandler = mainSession.prepareContentCaptureHandler(); mainSession.runOnContentCaptureThread(() -> mainSession.onSessionStarted(resultCode, binder)); } } + /** + * Prepares the content capture handler(i.e. the background thread). + * + * This is expected to be called from the {@link SessionStateReceiver#send} callback, after the + * session {@link performStart}. This is expected to be executed in a binder thread, instead + * of the UI thread. + */ + @NonNull + private Handler prepareContentCaptureHandler() { + if (mContentCaptureHandler == null) { + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareContentCaptureHandler"); + } + mContentCaptureHandler = BackgroundThread.getHandler(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + return mContentCaptureHandler; + } + /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) public MainContentCaptureSession( @NonNull ContentCaptureManager.StrippedContext context, @NonNull ContentCaptureManager manager, @NonNull Handler uiHandler, - @NonNull Handler contentCaptureHandler, @NonNull IContentCaptureManager systemServerInterface) { mContext = context; mManager = manager; mUiHandler = uiHandler; - mContentCaptureHandler = contentCaptureHandler; mSystemServerInterface = systemServerInterface; final int logHistorySize = mManager.mOptions.logHistorySize; @@ -260,18 +294,49 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } /** - * Starts this session. + * Performs the start of the session. + * + * This is expected to be called from the UI thread, when the activity finishes its first frame. + * This is a no-op if the session has already been started. + * + * See {@link #start(IBinder, IBinder, ComponentName, int)} for more details. + * + * @hide */ + @Override + public void performStart() { + if (!hasStarted() && mStartRunnable != null) { + mStartRunnable.run(); + } + } + + /** + * Creates a runnable to start this session. + * + * For performance reasons, it is better to only create a task to start the session + * during the creation of the activity and perform the actual start when the activity + * finishes it's first frame. */ @Override void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { - runOnContentCaptureThread( - () -> startImpl(token, shareableActivityToken, component, flags)); + if (Flags.postCreateAndroidBgThread()) { + mStartRunnable = () -> { + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "cc session startImpl"); + } + startImpl(token, shareableActivityToken, component, flags); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + }; + } else { + startImpl(token, shareableActivityToken, component, flags); + } } private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { - checkOnContentCaptureThread(); if (!isContentCaptureEnabled()) return; if (sVerbose) { @@ -305,11 +370,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } + @Override void onDestroy() { clearAndRunOnContentCaptureThread(() -> { try { - flush(FLUSH_REASON_SESSION_FINISHED); + internalFlush(FLUSH_REASON_SESSION_FINISHED); } finally { destroySession(); } @@ -557,11 +623,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL; } - flush(flushReason); + internalFlush(flushReason); } private boolean hasStarted() { - checkOnContentCaptureThread(); return mState != UNKNOWN_STATE; } @@ -575,6 +640,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet"); return; } + if (mContentCaptureHandler == null) { + Log.w(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): content capture " + + "thread not ready"); + return; + } if (mDisabled.get()) { // Should not be called on this state, as handleSendEvent checks. @@ -617,15 +687,18 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (sVerbose) Log.v(TAG, "Nothing to flush"); return; } - flush(reason); + internalFlush(reason); } - /** @hide */ + /** + * Internal API to flush the buffered events to the service. + * + * Do not confuse this with the public API {@link #flush()}. + * + * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override - public void flush(@FlushReason int reason) { - // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the - // public API. + public void internalFlush(@FlushReason int reason) { runOnContentCaptureThread(() -> flushImpl(reason)); } @@ -647,6 +720,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (!isContentCaptureReceiverEnabled()) { return; } + if (mContentCaptureHandler == null) { + Log.w(TAG, "handleForceFlush(" + getDebugState(reason) + "): content capture thread" + + "not ready"); + return; + } if (mDirectServiceInterface == null) { if (sVerbose) { @@ -763,7 +841,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mDirectServiceInterface = null; mContentProtectionEventProcessor = null; - mContentCaptureHandler.removeMessages(MSG_FLUSH); + if (mContentCaptureHandler != null) { + mContentCaptureHandler.removeMessages(MSG_FLUSH); + } } @Override @@ -917,6 +997,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * clear the buffer events then starting sending out current event. */ private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { + if (mContentCaptureHandler == null) { + mEventProcessQueue.offer(event); + return; + } if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) { // The buffer events are cleared in the same thread first to prevent new events // being added during the time of context switch. This would disrupt the sequence @@ -1119,6 +1203,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> */ private void checkOnContentCaptureThread() { + if (mContentCaptureHandler == null) { + Log.e(TAG, "content capture thread is not initiallized!"); + return; + } final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread(); if (!onContentCaptureThread) { mWrongThreadCount.incrementAndGet(); @@ -1139,6 +1227,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * </p> */ private void runOnContentCaptureThread(@NonNull Runnable r) { + if (mContentCaptureHandler == null) { + Log.e(TAG, "content capture thread is not initiallized!"); + // fall back to UI thread + runOnUiThread(r); + return; + } if (!mContentCaptureHandler.getLooper().isCurrentThread()) { mContentCaptureHandler.post(r); } else { @@ -1147,6 +1241,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { + if (mContentCaptureHandler == null) { + Log.e(TAG, "content capture thread is not initiallized!"); + // fall back to UI thread + runOnUiThread(r); + return; + } if (!mContentCaptureHandler.getLooper().isCurrentThread()) { mContentCaptureHandler.removeMessages(what); mContentCaptureHandler.post(r); diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index f709ed7f57cd..9df835098268 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -13,4 +13,16 @@ flag { namespace: "machine_learning" description: "Feature flag for baklava content capture API" bug: "380381249" + is_exported: true +} + +flag { + name: "post_create_android_bg_thread" + namespace: "pixel_state_server" + description: "Feature flag to post create the bg thread when an app is in the allowlist" + bug: "376468525" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 0a83bdc35b1f..8fef2d726b2c 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -766,7 +766,6 @@ public final class InputMethodInfo implements Parcelable { * Returns true if IME supports only virtual devices. * @hide */ - @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) @SystemApi public boolean isVirtualDeviceOnly() { return mIsVirtualDeviceOnly; diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index ed6ec32fca25..3cc0042f2bcc 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -16,6 +16,8 @@ package android.widget; +import static android.view.accessibility.Flags.triStateChecked; + import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -209,6 +211,10 @@ public abstract class CompoundButton extends Button implements Checkable { mCheckedFromResource = false; mChecked = checked; refreshDrawableState(); + if (triStateChecked()) { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED); + } // Avoid infinite recursions if setChecked() is called from a listener if (mBroadcasting) { @@ -490,7 +496,12 @@ public abstract class CompoundButton extends Button implements Checkable { public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); info.setCheckable(true); - info.setChecked(mChecked); + if (triStateChecked()) { + info.setChecked(mChecked ? AccessibilityNodeInfo.CHECKED_STATE_TRUE : + AccessibilityNodeInfo.CHECKED_STATE_FALSE); + } else { + info.setChecked(mChecked); + } } @Override diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 33890b80869d..f70bf9737636 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -1021,8 +1021,9 @@ public final class Magnifier { .setCallsite("InternalPopupWindow") .build(); - mBBQ = new BLASTBufferQueue("magnifier surface", mBbqSurfaceControl, - surfaceWidth, surfaceHeight, PixelFormat.TRANSLUCENT); + mBBQ = new BLASTBufferQueue("magnifier surface", /*updateDestinationFrame*/ true); + mBBQ.update(mBbqSurfaceControl, + surfaceWidth, surfaceHeight, PixelFormat.TRANSLUCENT); mSurface = mBBQ.createSurface(); // Setup the RenderNode tree. The root has two children, one containing the bitmap diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 2c21417fb790..ddbf9e49bb8d 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -211,7 +211,7 @@ public final class TransitionInfo implements Parcelable { FLAG_CONFIG_AT_END, FLAG_IS_TASK_DISPLAY_AREA, FLAG_FIRST_CUSTOM - }, flag = true) + }) public @interface ChangeFlags {} private final @TransitionType int mType; diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 644d69919998..9d7bedc4d0c3 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -689,14 +689,15 @@ public class IntentForwarderActivity extends Activity { } private void setMiniresolverPadding() { - Insets systemWindowInsets = - getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets( - WindowInsets.Type.systemBars()); - View buttonContainer = findViewById(R.id.button_bar_container); - buttonContainer.setPadding(0, 0, 0, - systemWindowInsets.bottom + getResources().getDimensionPixelOffset( - R.dimen.resolver_button_bar_spacing)); + if (buttonContainer != null) { + Insets systemWindowInsets = + getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets( + WindowInsets.Type.systemBars()); + buttonContainer.setPadding(0, 0, 0, + systemWindowInsets.bottom + getResources().getDimensionPixelOffset( + R.dimen.resolver_button_bar_spacing)); + } } @VisibleForTesting diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index a1945352ae09..db65d31f59da 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -929,8 +929,11 @@ public class ResolverActivity extends Activity implements if (shouldUseMiniResolver()) { View buttonContainer = findViewById(R.id.button_bar_container); - buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom - + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing)); + if (buttonContainer != null) { + buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom + + getResources().getDimensionPixelOffset( + R.dimen.resolver_button_bar_spacing)); + } } // Need extra padding so the list can fully scroll up diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 6ad7fef95357..e170d6652863 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -172,8 +172,7 @@ public class NativeLibraryHelper { private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath); private static native void nativeClose(long handle); - private static native long nativeSumNativeBinaries(long handle, String cpuAbi, - boolean debuggable); + private static native long nativeSumNativeBinaries(long handle, String cpuAbi); private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, String abiToCopy, boolean extractNativeLibs, boolean debuggable); @@ -188,7 +187,7 @@ public class NativeLibraryHelper { private static long sumNativeBinaries(Handle handle, String abi) { long sum = 0; for (long apkHandle : handle.apkHandles) { - sum += nativeSumNativeBinaries(apkHandle, abi, handle.debuggable); + sum += nativeSumNativeBinaries(apkHandle, abi); } return sum; } @@ -222,7 +221,7 @@ public class NativeLibraryHelper { public static int findSupportedAbi(Handle handle, String[] supportedAbis) { int finalRes = NO_NATIVE_LIBRARIES; for (long apkHandle : handle.apkHandles) { - final int res = nativeFindSupportedAbi(apkHandle, supportedAbis, handle.debuggable); + final int res = nativeFindSupportedAbi(apkHandle, supportedAbis); if (res == NO_NATIVE_LIBRARIES) { // No native code, keep looking through all APKs. } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) { @@ -244,8 +243,7 @@ public class NativeLibraryHelper { return finalRes; } - private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis, - boolean debuggable); + private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis); // Convenience method to call removeNativeBinariesFromDirLI(File) public static void removeNativeBinariesLI(String nativeLibraryPath) { diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index 1b770207f2cb..0d3c470b0e8a 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -817,12 +817,13 @@ public final class NotificationProgressBar extends ProgressBar implements if (part instanceof Segment segment) { final float segWidth = segment.mFraction * totalWidth; // Advance the start position to account for a point immediately prior. - final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x); + final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, + iPart == 1); final float start = x + startOffset; // Retract the end position to account for the padding and a point immediately // after. final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap, - segSegGap, x + segWidth, totalWidth, hasTrackerIcon); + segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon); final float end = x + segWidth - endOffset; drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded)); @@ -840,10 +841,10 @@ public final class NotificationProgressBar extends ProgressBar implements // Only shift the points right at the start/end. // For the points close to the start/end, the segment minimum width requirement // would take care of shifting them to be within the bounds. - if (x == 0) { + if (iPart == 0) { start = 0; end = pointWidth; - } else if (x == totalWidth) { + } else if (iPart == nParts - 1) { start = totalWidth - pointWidth; end = totalWidth; } @@ -856,14 +857,14 @@ public final class NotificationProgressBar extends ProgressBar implements } private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap, - float startX) { + boolean isSecondPart) { if (!(prevPart instanceof Point)) return 0F; - final float pointOffset = (startX == 0) ? pointRadius : 0; + final float pointOffset = isSecondPart ? pointRadius : 0; return pointOffset + pointRadius + segPointGap; } private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius, - float segPointGap, float segSegGap, float endX, float totalWidth, + float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth, boolean hasTrackerIcon) { if (nextPart == null) return 0F; if (nextPart instanceof Segment nextSeg) { @@ -874,7 +875,7 @@ public final class NotificationProgressBar extends ProgressBar implements return segSegGap; } - final float pointOffset = (endX == totalWidth) ? pointRadius : 0; + final float pointOffset = isSecondToLastPart ? pointRadius : 0; return segPointGap + pointRadius + pointOffset; } 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 26b0d11955d2..f5f4e4332d28 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -62,7 +62,7 @@ public class CoreDocument { // We also keep a more fine-grained BUILD number, exposed as // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD - static final float BUILD = 0.2f; + static final float BUILD = 0.3f; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); 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 9a37a22390a2..0b6a3c415e4a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -88,6 +88,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpM import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; @@ -97,6 +99,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation; @@ -111,6 +114,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; @@ -208,7 +212,9 @@ public class Operations { public static final int LAYOUT_CONTENT = 201; public static final int LAYOUT_BOX = 202; public static final int LAYOUT_ROW = 203; + public static final int LAYOUT_COLLAPSIBLE_ROW = 230; public static final int LAYOUT_COLUMN = 204; + public static final int LAYOUT_COLLAPSIBLE_COLUMN = 233; public static final int LAYOUT_CANVAS = 205; public static final int LAYOUT_CANVAS_CONTENT = 207; public static final int LAYOUT_TEXT = 208; @@ -218,6 +224,8 @@ public class Operations { public static final int MODIFIER_WIDTH = 16; public static final int MODIFIER_HEIGHT = 67; + public static final int MODIFIER_WIDTH_IN = 231; + public static final int MODIFIER_HEIGHT_IN = 232; public static final int MODIFIER_BACKGROUND = 55; public static final int MODIFIER_BORDER = 107; public static final int MODIFIER_PADDING = 58; @@ -324,6 +332,8 @@ public class Operations { map.put(MODIFIER_WIDTH, WidthModifierOperation::read); map.put(MODIFIER_HEIGHT, HeightModifierOperation::read); + map.put(MODIFIER_WIDTH_IN, WidthInModifierOperation::read); + map.put(MODIFIER_HEIGHT_IN, HeightInModifierOperation::read); map.put(MODIFIER_PADDING, PaddingModifierOperation::read); map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation::read); map.put(MODIFIER_BORDER, BorderModifierOperation::read); @@ -359,7 +369,9 @@ public class Operations { map.put(LAYOUT_CONTENT, LayoutComponentContent::read); map.put(LAYOUT_BOX, BoxLayout::read); map.put(LAYOUT_COLUMN, ColumnLayout::read); + map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read); map.put(LAYOUT_ROW, RowLayout::read); + map.put(LAYOUT_COLLAPSIBLE_ROW, CollapsibleRowLayout::read); map.put(LAYOUT_CANVAS, CanvasLayout::read); map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read); map.put(LAYOUT_TEXT, TextLayout::read); 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 39cc997fadc2..1cb8fefde80c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -86,6 +86,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LoopOper import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; @@ -691,6 +693,12 @@ public class RemoteComposeBuffer { return out; } + /** + * Append a path to an existing path + * + * @param id id of the path to append to + * @param path the path to append + */ public void pathAppend(int id, float... path) { PathAppend.apply(mBuffer, id, path); } @@ -772,8 +780,8 @@ public class RemoteComposeBuffer { * @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 contextStart the context start + * @param contextEnd the context end * @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 @@ -798,8 +806,8 @@ public class RemoteComposeBuffer { * @param textId 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 contextStart the context start + * @param contextEnd the context end * @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 @@ -986,6 +994,11 @@ public class RemoteComposeBuffer { /////////////////////////////////////////////////////////////////////////////////////////////// + /** + * inflate the buffer into a list of operations + * + * @param operations the operations list to add to + */ public void inflateFromBuffer(@NonNull ArrayList<Operation> operations) { mBuffer.setIndex(0); while (mBuffer.available()) { @@ -1001,6 +1014,12 @@ public class RemoteComposeBuffer { } } + /** + * Read the next operation from the buffer + * + * @param buffer The buff to read + * @param operations the operations list to add to + */ public static void readNextOperation( @NonNull WireBuffer buffer, @NonNull ArrayList<Operation> operations) { int opId = buffer.readByte(); @@ -1014,6 +1033,11 @@ public class RemoteComposeBuffer { operation.read(buffer, operations); } + /** + * copy the current buffer to a new one + * + * @return A new RemoteComposeBuffer + */ @NonNull RemoteComposeBuffer copy() { ArrayList<Operation> operations = new ArrayList<>(); @@ -1022,6 +1046,11 @@ public class RemoteComposeBuffer { return copyFromOperations(operations, buffer); } + /** + * add a set theme + * + * @param theme The theme to set + */ public void setTheme(int theme) { Theme.apply(mBuffer, theme); } @@ -1040,6 +1069,14 @@ public class RemoteComposeBuffer { return buffer; } + /** + * Create a RemoteComposeBuffer from a file + * + * @param file A file + * @param remoteComposeState The RemoteComposeState + * @return A RemoteComposeBuffer + * @throws IOException if the file cannot be read + */ @NonNull public RemoteComposeBuffer fromFile( @NonNull File file, @NonNull RemoteComposeState remoteComposeState) throws IOException { @@ -1048,6 +1085,13 @@ public class RemoteComposeBuffer { return buffer; } + /** + * Create a RemoteComposeBuffer from an InputStream + * + * @param inputStream An InputStream + * @param remoteComposeState The RemoteComposeState + * @return A RemoteComposeBuffer + */ @NonNull public static RemoteComposeBuffer fromInputStream( @NonNull InputStream inputStream, @NonNull RemoteComposeState remoteComposeState) { @@ -1056,6 +1100,13 @@ public class RemoteComposeBuffer { return buffer; } + /** + * Create a RemoteComposeBuffer from an array of operations + * + * @param operations An array of operations + * @param buffer A RemoteComposeBuffer + * @return A RemoteComposeBuffer + */ @NonNull RemoteComposeBuffer copyFromOperations( @NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) { @@ -1834,12 +1885,12 @@ public class RemoteComposeBuffer { /** * Add a marquee modifier * - * @param iterations - * @param animationMode - * @param repeatDelayMillis - * @param initialDelayMillis - * @param spacing - * @param velocity + * @param iterations number of iterations + * @param animationMode animation mode + * @param repeatDelayMillis repeat delay + * @param initialDelayMillis initial delay + * @param spacing spacing between items + * @param velocity velocity of the marquee */ public void addModifierMarquee( int iterations, @@ -1861,14 +1912,21 @@ public class RemoteComposeBuffer { /** * Add a graphics layer * - * @param scaleX - * @param scaleY - * @param rotationX - * @param rotationY - * @param rotationZ - * @param shadowElevation - * @param transformOriginX - * @param transformOriginY + * @param scaleX scale x + * @param scaleY scale y + * @param rotationX rotation in X + * @param rotationY rotation in Y + * @param rotationZ rotation in Z + * @param shadowElevation shadow elevation + * @param transformOriginX transform origin x + * @param transformOriginY transform origin y + * @param alpha alpha value + * @param cameraDistance camera distance + * @param blendMode blend mode + * @param spotShadowColorId spot shadow color + * @param ambientShadowColorId ambient shadow color + * @param colorFilterId id of color filter + * @param renderEffectId id of render effect */ public void addModifierGraphicsLayer( float scaleX, @@ -1923,14 +1981,32 @@ public class RemoteComposeBuffer { ClipRectModifierOperation.apply(mBuffer); } + /** + * add start of loop + * + * @param indexId id of the variable + * @param from start value + * @param step step value + * @param until stop value + */ public void addLoopStart(int indexId, float from, float step, float until) { LoopOperation.apply(mBuffer, indexId, from, step, until); } + /** Add a loop end */ public void addLoopEnd() { ContainerEnd.apply(mBuffer); } + /** + * add a state layout + * + * @param componentId id of the state + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + * @param indexId index of the state + */ public void addStateLayout( int componentId, int animationId, int horizontal, int vertical, int indexId) { mLastComponentId = getComponentId(componentId); @@ -1966,6 +2042,22 @@ public class RemoteComposeBuffer { } /** + * Add a row start tag + * + * @param componentId component id + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + * @param spacedBy spacing between items + */ + public void addCollapsibleRowStart( + int componentId, int animationId, int horizontal, int vertical, float spacedBy) { + mLastComponentId = getComponentId(componentId); + CollapsibleRowLayout.apply( + mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy); + } + + /** * Add a column start tag * * @param componentId component id @@ -1981,6 +2073,22 @@ public class RemoteComposeBuffer { } /** + * Add a column start tag + * + * @param componentId component id + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + * @param spacedBy spacing between items + */ + public void addCollapsibleColumnStart( + int componentId, int animationId, int horizontal, int vertical, float spacedBy) { + mLastComponentId = getComponentId(componentId); + CollapsibleColumnLayout.apply( + mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy); + } + + /** * Add a canvas start tag * * @param componentId component id diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java index 43f8ea7dc78f..363b82bdf70c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -67,8 +67,8 @@ public class RemoteComposeState implements CollectionsAccess { * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be * accessed with this command * - * @param id - * @return + * @param id the id of the object + * @return the object */ @Nullable public Object getFromId(int id) { @@ -78,8 +78,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * true if the cache contain this id * - * @param id - * @return + * @param id the id of the object + * @return true if the cache contain this id */ public boolean containsId(int id) { return mIntDataMap.get(id) != null; @@ -138,8 +138,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Get the path asociated with the Data * - * @param id - * @return + * @param id of path + * @return path object */ public Object getPath(int id) { return mPathMap.get(id); @@ -180,7 +180,7 @@ public class RemoteComposeState implements CollectionsAccess { /** * Adds a data Override. * - * @param id + * @param id the id of the data * @param item the new value */ public void overrideData(int id, @NonNull Object item) { @@ -222,8 +222,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Adds a float Override. * - * @param id - * @param value the new value + * @param id The id of the float + * @param value the override value */ public void overrideFloat(int id, float value) { float previous = mFloatMap.get(id); @@ -235,7 +235,12 @@ public class RemoteComposeState implements CollectionsAccess { } } - /** Insert an item in the cache */ + /** + * Insert an item in the cache + * + * @param item integer item to cache + * @return the id of the integer + */ public int cacheInteger(int item) { int id = nextId(); mIntegerMap.put(id, item); @@ -243,7 +248,12 @@ public class RemoteComposeState implements CollectionsAccess { return id; } - /** Insert an integer item in the cache */ + /** + * Insert an integer item in the cache + * + * @param id the id of the integer + * @param value the value of the integer + */ public void updateInteger(int id, int value) { if (!mIntegerOverride[id]) { int previous = mIntegerMap.get(id); @@ -292,10 +302,10 @@ public class RemoteComposeState implements CollectionsAccess { } /** - * Get the float value + * Get the color from the cache * - * @param id - * @return + * @param id The id of the color + * @return The color */ public int getColor(int id) { return mColorMap.get(id); @@ -377,6 +387,9 @@ public class RemoteComposeState implements CollectionsAccess { /** * Method to determine if a cached value has been written to the documents WireBuffer based on * its id. + * + * @param id id to check + * @return true if the value has not been written to the WireBuffer */ public boolean wasNotWritten(int id) { return !mIntWrittenMap.get(id); @@ -406,7 +419,7 @@ public class RemoteComposeState implements CollectionsAccess { * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is * collections * - * @return + * @return return a unique id in the set */ public int nextId(int type) { if (0 == type) { @@ -418,7 +431,7 @@ public class RemoteComposeState implements CollectionsAccess { /** * Set the next id * - * @param id + * @param id set the id to increment off of */ public void setNextId(int id) { mNextId = id; @@ -440,8 +453,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Commands that listen to variables add themselves. * - * @param id - * @param variableSupport + * @param id id of variable to listen to + * @param variableSupport command that listens to variable */ public void listenToVar(int id, @NonNull VariableSupport variableSupport) { add(id, variableSupport); @@ -450,8 +463,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Is any command listening to this variable * - * @param id - * @return + * @param id The Variable id + * @return true if any command is listening to this variable */ public boolean hasListener(int id) { return mVarListeners.get(id) != null; @@ -460,8 +473,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * List of Commands that need to be updated * - * @param context - * @return + * @param context The context + * @return The number of ops to update */ public int getOpsToUpdate(@NonNull RemoteContext context) { if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) { @@ -479,7 +492,7 @@ public class RemoteComposeState implements CollectionsAccess { /** * Set the width of the overall document on screen. * - * @param width + * @param width the width of the document in pixels */ public void setWindowWidth(float width) { updateFloat(RemoteContext.ID_WINDOW_WIDTH, width); @@ -488,12 +501,18 @@ public class RemoteComposeState implements CollectionsAccess { /** * Set the width of the overall document on screen. * - * @param height + * @param height the height of the document in pixels */ public void setWindowHeight(float height) { updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height); } + /** + * Add an array access + * + * @param id The id of the array Access + * @param collection The array access + */ public void addCollection(int id, @NonNull ArrayAccess collection) { mCollectionMap.put(id & 0xFFFFF, collection); } @@ -513,10 +532,22 @@ public class RemoteComposeState implements CollectionsAccess { return mCollectionMap.get(id & 0xFFFFF).getId(index); } + /** + * adds a DataMap to the cache + * + * @param id The id of the data map + * @param map The data map + */ public void putDataMap(int id, @NonNull DataMap map) { mDataMapMap.put(id, map); } + /** + * Get the DataMap asociated with the id + * + * @param id the id of the DataMap + * @return the DataMap + */ public @Nullable DataMap getDataMap(int id) { return mDataMapMap.get(id); } @@ -526,15 +557,32 @@ public class RemoteComposeState implements CollectionsAccess { return mCollectionMap.get(id & 0xFFFFF).getLength(); } + /** + * sets the RemoteContext + * + * @param context the context + */ public void setContext(@NonNull RemoteContext context) { mRemoteContext = context; mRemoteContext.clearLastOpCount(); } + /** + * Add an object to the cache. Uses the id for the item and adds it to the cache based + * + * @param id the id of the object + * @param value the object + */ public void updateObject(int id, @NonNull Object value) { mObjectMap.put(id, value); } + /** + * Get an object from the cache + * + * @param id The id of the object + * @return The object + */ public @Nullable Object getObject(int id) { return mObjectMap.get(id); } 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 23c362830713..36e4ec1ff303 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -47,7 +47,7 @@ public abstract class RemoteContext { new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@ @Nullable protected PaintContext mPaintContext = null; - protected float mDensity = 2.75f; + protected float mDensity = Float.NaN; @NonNull ContextMode mMode = ContextMode.UNSET; @@ -77,7 +77,7 @@ public abstract class RemoteContext { * @param density */ public void setDensity(float density) { - if (density > 0) { + if (!Float.isNaN(density) && density > 0) { mDensity = density; } } @@ -234,23 +234,60 @@ public abstract class RemoteContext { */ public abstract void addCollection(int id, @NonNull ArrayAccess collection); + /** + * put DataMap under an id + * + * @param id the id of the DataMap + * @param map the DataMap + */ public abstract void putDataMap(int id, @NonNull DataMap map); + /** + * Get a DataMap given an id + * + * @param id the id of the DataMap + * @return the DataMap + */ public abstract @Nullable DataMap getDataMap(int id); + /** + * Run an action + * + * @param id the id of the action + * @param metadata the metadata of the action + */ public abstract void runAction(int id, @NonNull String metadata); // TODO: we might add an interface to group all valid parameter types + + /** + * Run an action with a named parameter + * + * @param textId the text id of the action + * @param value the value of the parameter + */ public abstract void runNamedAction(int textId, Object value); + /** + * Put an object under an id + * + * @param mId the id of the object + * @param command the object + */ public abstract void putObject(int mId, @NonNull Object command); + /** + * Get an object given an id + * + * @param mId the id of the object + * @return the object + */ public abstract @Nullable Object getObject(int mId); /** * Add a touch listener to the context * - * @param touchExpression + * @param touchExpression the touch expression */ public void addTouchListener(TouchListener touchExpression) {} @@ -668,11 +705,24 @@ public abstract class RemoteContext { /////////////////////////////////////////////////////////////////////////////////////////////// // Click handling /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Is this a time id float + * + * @param fl the floatId to test + * @return true if it is a time id + */ public static boolean isTime(float fl) { int value = Utils.idFromNan(fl); return value >= ID_CONTINUOUS_SEC && value <= ID_DAY_OF_MONTH; } + /** + * get the time from a float id that indicates a type of time + * + * @param fl id of the type of time information requested + * @return various time information such as seconds or min + */ public static float getTime(float fl) { LocalDateTime dateTime = LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly? @@ -716,6 +766,17 @@ public abstract class RemoteContext { return fl; } + /** + * Add a click area to the doc + * + * @param id the id of the click area + * @param contentDescription the content description of the click area + * @param left the left bounds of the click area + * @param top the top bounds of the click area + * @param right the right bounds of the click area + * @param bottom the + * @param metadataId the id of the metadata string + */ public abstract void addClickArea( int id, int contentDescription, 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 index b55f25c911fe..06ef9979a267 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java @@ -102,6 +102,12 @@ public class ClipPath extends PaintOperation { return OP_CODE; } + /** + * Apply this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id of the path + */ public static void apply(@NonNull WireBuffer buffer, int id) { buffer.start(OP_CODE); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java index ac6271c6328e..7a72b109b2a8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java @@ -70,6 +70,13 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues); } + /** + * Write this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id of the array + * @param values the values of the array + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) { buffer.start(OP_CODE); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java index 47cbff36d492..7e29620ec104 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java @@ -62,6 +62,13 @@ public class DataListIds extends Operation implements VariableSupport, ArrayAcce return "map[" + Utils.idString(mId) + "] \"" + Arrays.toString(mIds) + "\""; } + /** + * Write this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id of the array + * @param ids the values of the array + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) { buffer.start(OP_CODE); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java index ff85721027f7..33752e0b2134 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java @@ -89,6 +89,15 @@ public class DataMapIds extends Operation { return builder.toString(); } + /** + * Write this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id + * @param names the names of the variables + * @param type the types of the variables + * @param ids the ids of the variables + */ public static void apply( @NonNull WireBuffer buffer, int id, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java index c1e2e662ca80..7f1ba6f94065 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java @@ -31,8 +31,8 @@ import java.util.List; /** Base class for commands that take 3 float */ public abstract class DrawBase2 extends PaintOperation implements VariableSupport { @NonNull protected String mName = "DrawRectBase"; - float mV1; - float mV2; + protected float mV1; + protected float mV2; float mValue1; float mValue2; @@ -76,6 +76,13 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor return mName + " " + floatToString(mV1) + " " + floatToString(mV2); } + /** + * Read this operation and add it to the list of operations + * + * @param maker the maker of the operation + * @param buffer the buffer to read + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java index 6fedea3245a2..a6bfda8beccd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java @@ -92,6 +92,13 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor + floatToString(mV3); } + /** + * Read this operation and add it to the list of operations + * + * @param maker the maker of the operation + * @param buffer the buffer to read + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java index aa9cc68e6552..1e96bcd9cebf 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java @@ -102,6 +102,13 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor + floatToString(mY2Value, mY2); } + /** + * Read this operation and add it to the list of operations + * + * @param maker the maker of the operation + * @param buffer the buffer to read + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java index 64c2730e5f9a..bc5904584527 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java @@ -116,6 +116,13 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6); } + /** + * Read this operation and add it to the list of operations + * + * @param build interface to construct the component + * @param buffer the buffer to read from + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float sv1 = buffer.readFloat(); @@ -132,13 +139,13 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor /** * writes out a the operation to the buffer. * - * @param v1 - * @param v2 - * @param v3 - * @param v4 - * @param v5 - * @param v6 - * @return + * @param v1 the first parameter + * @param v2 the second parameter + * @param v3 the third parameter + * @param v4 the fourth parameter + * @param v5 the fifth parameter + * @param v6 the sixth parameter + * @return the operation */ @Nullable public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) { 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 index cdb527dee460..40d3bede0912 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java @@ -137,6 +137,17 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * Writes out the operation to the buffer + * + * @param buffer the buffer to write to + * @param id the id of the Bitmap + * @param left left most x coordinate + * @param top top most y coordinate + * @param right right most x coordinate + * @param bottom bottom most y coordinate + * @param descriptionId string id of the description + */ public static void apply( @NonNull WireBuffer buffer, int id, 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 638fe148d746..013dd1ae9db8 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 @@ -131,6 +131,21 @@ public class DrawBitmapInt extends PaintOperation implements AccessibleComponent return OP_CODE; } + /** + * Draw a bitmap using integer coordinates + * + * @param buffer the buffer to write to + * @param imageId the id of the bitmap + * @param srcLeft the left most pixel in the bitmap + * @param srcTop the top most pixel in the bitmap + * @param srcRight the right most pixel in the bitmap + * @param srcBottom the bottom most pixel in the bitmap + * @param dstLeft the left most pixel in the destination + * @param dstTop the top most pixel in the destination + * @param dstRight the right most pixel in the destination + * @param dstBottom the bottom most pixel in the destination + * @param cdId the content discription id + */ public static void apply( @NonNull WireBuffer buffer, int imageId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java index d6467c926747..e1070f97d5aa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java @@ -217,6 +217,23 @@ public class DrawBitmapScaled extends PaintOperation return OP_CODE; } + /** + * Draw a bitmap using integer coordinates + * + * @param buffer the buffer to write to + * @param imageId the id of the image + * @param srcLeft the left most pixel in the image to draw + * @param srcTop the right most pixel in the image to draw + * @param srcRight the right most pixel in the image to draw + * @param srcBottom the bottom most pixel in the image to draw + * @param dstLeft the left most pixel in the destination + * @param dstTop the top most pixel in the destination + * @param dstRight the right most pixel in the destination + * @param dstBottom the bottom most pixel in the destination + * @param scaleType the type of scale operation + * @param scaleFactor the scalefactor to use with fixed scale + * @param cdId the content discription id + */ public static void apply( @NonNull WireBuffer buffer, int imageId, 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 index 398cf4892e12..db9c4d3efafa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java @@ -81,6 +81,12 @@ public class DrawPath extends PaintOperation { return Operations.DRAW_PATH; } + /** + * Draw a path + * + * @param buffer the buffer to write to + * @param id the id of the path + */ public static void apply(@NonNull WireBuffer buffer, int id) { buffer.start(Operations.DRAW_PATH); buffer.writeInt(id); 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 index 86f3c992f2fb..3ab4a87c614c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java @@ -117,6 +117,15 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { return Operations.DRAW_TEXT_ON_PATH; } + /** + * add a draw text on path operation to the buffer + * + * @param buffer the buffer to add to + * @param textId the id of the text string + * @param pathId the id of the path + * @param hOffset the horizontal offset to position the string + * @param vOffset the vertical offset to position the string + */ public static void apply( @NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) { buffer.start(OP_CODE); 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 index d4d4a5ecf6b9..e2883949022c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java @@ -127,6 +127,16 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { return Operations.DRAW_TWEEN_PATH; } + /** + * add a draw tween path operation to the buffer + * + * @param buffer the buffer to add to + * @param path1Id the first path + * @param path2Id the second path + * @param tween the amount of the tween + * @param start the start sub range to draw + * @param stop the end of the sub range to draw + */ public static void apply( @NonNull WireBuffer buffer, int path1Id, 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 index 044430d1e3c1..66daa13dd21c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java @@ -74,6 +74,11 @@ public class MatrixRestore extends PaintOperation { return OP_CODE; } + /** + * add a matrix restore operation to the buffer + * + * @param buffer the buffer to add to + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } 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 index aec316aea361..ec918e8260b9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java @@ -72,6 +72,11 @@ public class MatrixSave extends PaintOperation { return OP_CODE; } + /** + * add a matrix save operation to the buffer + * + * @param buffer the buffer to add to + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(Operations.MATRIX_SAVE); } 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 index daf2c5502c5d..f756b76b86c3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.paint.PaintBund import java.util.List; +/** Paint data operation */ public class PaintData extends PaintOperation implements VariableSupport { private static final int OP_CODE = Operations.PAINT_VALUES; private static final String CLASS_NAME = "PaintData"; @@ -80,6 +81,12 @@ public class PaintData extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * add a paint data to the buffer + * + * @param buffer the buffer to add to + * @param paintBundle the paint bundle + */ public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) { buffer.start(Operations.PAINT_VALUES); paintBundle.writeBundle(buffer); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java index 7ff879e41cac..e7cce03f0c4b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java @@ -101,6 +101,7 @@ public class PathAppend extends PaintOperation implements VariableSupport { public static final int CUBIC = 14; public static final int CLOSE = 15; public static final int DONE = 16; + public static final int RESET = 17; 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); @@ -108,6 +109,7 @@ public class PathAppend extends PaintOperation implements VariableSupport { 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 final float RESET_NAN = Utils.asNan(RESET); /** * The name of the class @@ -128,6 +130,14 @@ public class PathAppend extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * add a path append operation to the buffer. With PathCreate allows you create a path + * dynamically + * + * @param buffer add the data to this buffer + * @param id id of the path + * @param data the path data to append + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) { buffer.start(OP_CODE); buffer.writeInt(id); @@ -175,6 +185,10 @@ public class PathAppend extends PaintOperation implements VariableSupport { public void apply(@NonNull RemoteContext context) { float[] data = context.getPathData(mInstanceId); float[] out = mOutputPath; + if (Float.floatToRawIntBits(out[0]) == Float.floatToRawIntBits(RESET_NAN)) { + context.loadPathData(mInstanceId, new float[0]); + return; + } if (data != null) { out = new float[data.length + mOutputPath.length]; @@ -190,6 +204,12 @@ public class PathAppend extends PaintOperation implements VariableSupport { context.loadPathData(mInstanceId, out); } + /** + * Convert a path to a string + * + * @param path the path to convert + * @return text representation of path + */ @NonNull public static String pathString(@Nullable float[] path) { if (path == null) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java index 75562cd8fb4c..1f76639b1b1f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java @@ -131,6 +131,14 @@ public class PathCreate extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * add a create path operation + * + * @param buffer buffer to add to + * @param id the id of the path + * @param startX the start x of the path (moveTo x,y) + * @param startY the start y of the path (moveTo x,y) + */ public static void apply(@NonNull WireBuffer buffer, int id, float startX, float startY) { buffer.start(OP_CODE); buffer.writeInt(id); @@ -165,6 +173,12 @@ public class PathCreate extends PaintOperation implements VariableSupport { .field(FLOAT, "startX", "initial start y"); } + /** + * convert a path to a string + * + * @param path path to convert (expressed as an array of floats) + * @return the text representing the path + */ @NonNull public static String pathString(@Nullable float[] path) { if (path == null) { 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 index 85a01fc7cbc7..45d99a716443 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java @@ -131,6 +131,13 @@ public class PathData extends Operation implements VariableSupport { return OP_CODE; } + /** + * add a create path operation + * + * @param buffer buffer to add to + * @param id the id of the path + * @param data the path + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) { buffer.start(Operations.DATA_PATH); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java index d48de37996ee..5788d8f4da64 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java @@ -73,6 +73,13 @@ public class TextData extends Operation implements SerializableToString { return OP_CODE; } + /** + * add a text data operation + * + * @param buffer buffer to add to + * @param textId the id for the text + * @param text the data to encode + */ public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) { buffer.start(OP_CODE); buffer.writeInt(textId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java index 37ea567f5913..a6570a371f15 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java @@ -50,6 +50,11 @@ public class TextLength extends Operation { return CLASS_NAME + "[" + mLengthId + "] = " + mTextId; } + /** + * The name of the class + * + * @return the name + */ public static @NonNull String name() { return CLASS_NAME; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java index d51b38924126..58cd68e2a5db 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java @@ -66,6 +66,11 @@ public class TextMeasure extends PaintOperation { return "FloatConstant[" + mId + "] = " + mTextId + " " + mType; } + /** + * The name of the class + * + * @return the name + */ public static @NonNull String name() { return CLASS_NAME; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java index e71cb9a51830..dcd334822010 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java @@ -36,10 +36,12 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; @@ -204,6 +206,9 @@ public class LayoutComponent extends Component { mPaddingRight = 0f; mPaddingBottom = 0f; + WidthInModifierOperation widthInConstraints = null; + HeightInModifierOperation heightInConstraints = null; + for (OperationInterface op : mComponentModifiers.getList()) { if (op instanceof PaddingModifierOperation) { // We are accumulating padding modifiers to compute the margin @@ -221,6 +226,10 @@ public class LayoutComponent extends Component { mWidthModifier = (WidthModifierOperation) op; } else if (op instanceof HeightModifierOperation && mHeightModifier == null) { mHeightModifier = (HeightModifierOperation) op; + } else if (op instanceof WidthInModifierOperation) { + widthInConstraints = (WidthInModifierOperation) op; + } else if (op instanceof HeightInModifierOperation) { + heightInConstraints = (HeightInModifierOperation) op; } else if (op instanceof ZIndexModifierOperation) { mZIndexModifier = (ZIndexModifierOperation) op; } else if (op instanceof GraphicsLayerModifierOperation) { @@ -241,6 +250,12 @@ public class LayoutComponent extends Component { if (mHeightModifier == null) { mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP); } + if (widthInConstraints != null) { + mWidthModifier.setWidthIn(widthInConstraints); + } + if (heightInConstraints != null) { + mHeightModifier.setHeightIn(heightInConstraints); + } setWidth(computeModifierDefinedWidth(null)); setHeight(computeModifierDefinedHeight(null)); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java new file mode 100644 index 000000000000..afc41b1873ef --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.layout.managers; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +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.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; + +import java.util.List; + +public class CollapsibleColumnLayout extends ColumnLayout { + + public CollapsibleColumnLayout( + @Nullable Component parent, + int componentId, + int animationId, + float x, + float y, + float width, + float height, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + super( + parent, + componentId, + animationId, + x, + y, + width, + height, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + public CollapsibleColumnLayout( + @Nullable Component parent, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + super( + parent, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + @NonNull + @Override + protected String getSerializedName() { + return "COLLAPSIBLE_COLUMN"; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return Operations.LAYOUT_COLLAPSIBLE_COLUMN; + } + + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ + public static void apply( + @NonNull WireBuffer buffer, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + buffer.start(id()); + buffer.writeInt(componentId); + buffer.writeInt(animationId); + buffer.writeInt(horizontalPositioning); + buffer.writeInt(verticalPositioning); + buffer.writeFloat(spacedBy); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int componentId = buffer.readInt(); + int animationId = buffer.readInt(); + int horizontalPositioning = buffer.readInt(); + int verticalPositioning = buffer.readInt(); + float spacedBy = buffer.readFloat(); + operations.add( + new CollapsibleColumnLayout( + null, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy)); + } + + @Override + protected boolean hasVerticalIntrinsicDimension() { + return true; + } + + @Override + public void computeWrapSize( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @NonNull Size size) { + super.computeWrapSize( + context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size); + } + + @Override + public boolean applyVisibility( + float selfWidth, float selfHeight, @NonNull MeasurePass measure) { + float childrenWidth = 0f; + float childrenHeight = 0f; + boolean changedVisibility = false; + for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + if (childrenHeight + childMeasure.getH() > selfHeight) { + childMeasure.setVisibility(Visibility.GONE); + changedVisibility = true; + } else { + childrenHeight += childMeasure.getH(); + childrenWidth = Math.max(childrenWidth, childMeasure.getW()); + } + } + return changedVisibility; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java new file mode 100644 index 000000000000..0e7eb8676f46 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.layout.managers; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +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.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; + +import java.util.List; + +public class CollapsibleRowLayout extends RowLayout { + + public CollapsibleRowLayout( + @Nullable Component parent, + int componentId, + int animationId, + float x, + float y, + float width, + float height, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + super( + parent, + componentId, + animationId, + x, + y, + width, + height, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + public CollapsibleRowLayout( + @Nullable Component parent, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + super( + parent, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + @NonNull + @Override + protected String getSerializedName() { + return "COLLAPSIBLE_ROW"; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return Operations.LAYOUT_COLLAPSIBLE_ROW; + } + + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ + public static void apply( + @NonNull WireBuffer buffer, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + buffer.start(id()); + buffer.writeInt(componentId); + buffer.writeInt(animationId); + buffer.writeInt(horizontalPositioning); + buffer.writeInt(verticalPositioning); + buffer.writeFloat(spacedBy); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int componentId = buffer.readInt(); + int animationId = buffer.readInt(); + int horizontalPositioning = buffer.readInt(); + int verticalPositioning = buffer.readInt(); + float spacedBy = buffer.readFloat(); + operations.add( + new CollapsibleRowLayout( + null, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy)); + } + + @Override + protected boolean hasHorizontalIntrinsicDimension() { + return true; + } + + @Override + public void computeWrapSize( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @NonNull Size size) { + super.computeWrapSize( + context, Float.MAX_VALUE, maxHeight, horizontalWrap, verticalWrap, measure, size); + } + + @Override + public boolean applyVisibility( + float selfWidth, float selfHeight, @NonNull MeasurePass measure) { + float childrenWidth = 0f; + float childrenHeight = 0f; + boolean changedVisibility = false; + for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + if (childrenWidth + childMeasure.getW() > selfWidth) { + childMeasure.setVisibility(Visibility.GONE); + changedVisibility = true; + } else { + childrenWidth += childMeasure.getW(); + childrenHeight = Math.max(childrenHeight, childMeasure.getH()); + } + } + return changedVisibility; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index f68d7b439578..4d0cbefb0c92 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; import java.util.List; @@ -93,7 +94,8 @@ public class ColumnLayout extends LayoutManager { @NonNull @Override public String toString() { - return "COLUMN [" + return getSerializedName() + + " [" + mComponentId + ":" + mAnimationId @@ -213,41 +215,62 @@ public class ColumnLayout extends LayoutManager { selfHeight = mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom; } - boolean hasWeights = false; - float totalWeights = 0f; - for (Component child : mChildrenComponents) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; - } - if (child instanceof LayoutComponent - && ((LayoutComponent) child).getHeightModifier().hasWeight()) { - hasWeights = true; - totalWeights += ((LayoutComponent) child).getHeightModifier().getValue(); - } else { - childrenHeight += childMeasure.getH(); - } - } - if (hasWeights) { - float availableSpace = selfHeight - childrenHeight; + boolean checkWeights = true; + while (checkWeights) { + checkWeights = false; + boolean hasWeights = false; + float totalWeights = 0f; for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } if (child instanceof LayoutComponent && ((LayoutComponent) child).getHeightModifier().hasWeight()) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; + hasWeights = true; + totalWeights += ((LayoutComponent) child).getHeightModifier().getValue(); + } else { + childrenHeight += childMeasure.getH(); + } + } + if (hasWeights) { + float availableSpace = selfHeight - childrenHeight; + for (Component child : mChildrenComponents) { + if (child instanceof LayoutComponent + && ((LayoutComponent) child).getHeightModifier().hasWeight()) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + float weight = ((LayoutComponent) child).getHeightModifier().getValue(); + float childHeight = (weight * availableSpace) / totalWeights; + HeightInModifierOperation heightInConstraints = + ((LayoutComponent) child).getHeightModifier().getHeightIn(); + if (heightInConstraints != null) { + float min = heightInConstraints.getMin(); + float max = heightInConstraints.getMax(); + if (min != -1) { + childHeight = Math.max(min, childHeight); + } + if (max != -1) { + childHeight = Math.min(max, childHeight); + } + } + childMeasure.setH(childHeight); + child.measure( + context, + childMeasure.getW(), + childMeasure.getW(), + childMeasure.getH(), + childMeasure.getH(), + measure); } - float weight = ((LayoutComponent) child).getHeightModifier().getValue(); - childMeasure.setH((weight * availableSpace) / totalWeights); - child.measure( - context, - childMeasure.getW(), - childMeasure.getW(), - childMeasure.getH(), - childMeasure.getH(), - measure); } } + + if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) { + checkWeights = true; + } } childrenHeight = 0f; @@ -360,6 +383,16 @@ public class ColumnLayout extends LayoutManager { return Operations.LAYOUT_COLUMN; } + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -367,7 +400,7 @@ public class ColumnLayout extends LayoutManager { int horizontalPositioning, int verticalPositioning, float spacedBy) { - buffer.start(Operations.LAYOUT_COLUMN); + buffer.start(id()); buffer.writeInt(componentId); buffer.writeInt(animationId); buffer.writeInt(horizontalPositioning); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index edfd69cbfa96..8b52bbe5cdf8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -43,6 +43,18 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl super(parent, componentId, animationId, x, y, width, height); } + /** + * Allows layout managers to override elements visibility + * + * @param selfWidth intrinsic width of the layout manager content + * @param selfHeight intrinsic height of the layout manager content + * @param measure measure pass + */ + public boolean applyVisibility( + float selfWidth, float selfHeight, @NonNull MeasurePass measure) { + return false; + } + /** Implemented by subclasses to provide a layout/measure pass */ public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) { // nothing here @@ -197,7 +209,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl } if (!hasWrap) { - if (hasHorizontalScroll()) { + if (hasHorizontalIntrinsicDimension()) { mCachedWrapSize.setWidth(0f); mCachedWrapSize.setHeight(0f); computeWrapSize( @@ -210,15 +222,19 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl mCachedWrapSize); float w = mCachedWrapSize.getWidth(); computeSize(context, 0f, w, 0, measuredHeight, measure); - mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w); - } else if (hasVerticalScroll()) { + if (hasHorizontalScroll()) { + mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w); + } + } else if (hasVerticalIntrinsicDimension()) { mCachedWrapSize.setWidth(0f); mCachedWrapSize.setHeight(0f); computeWrapSize( context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize); float h = mCachedWrapSize.getHeight(); computeSize(context, 0f, measuredWidth, 0, h, measure); - mComponentModifiers.setVerticalScrollDimension(measuredHeight, h); + if (hasVerticalScroll()) { + mComponentModifiers.setVerticalScrollDimension(measuredHeight, h); + } } else { float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight; float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom; @@ -246,6 +262,14 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl return mComponentModifiers.hasHorizontalScroll(); } + protected boolean hasHorizontalIntrinsicDimension() { + return hasHorizontalScroll(); + } + + protected boolean hasVerticalIntrinsicDimension() { + return hasVerticalScroll(); + } + private boolean hasVerticalScroll() { return mComponentModifiers.hasVerticalScroll(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index b688f6e4175a..5b35c4c70702 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; import java.util.List; @@ -91,7 +92,8 @@ public class RowLayout extends LayoutManager { @NonNull @Override public String toString() { - return "ROW [" + return getSerializedName() + + " [" + mComponentId + ":" + mAnimationId @@ -212,44 +214,66 @@ public class RowLayout extends LayoutManager { mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom; } - boolean hasWeights = false; - float totalWeights = 0f; - for (Component child : mChildrenComponents) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; - } - if (child instanceof LayoutComponent - && ((LayoutComponent) child).getWidthModifier().hasWeight()) { - hasWeights = true; - totalWeights += ((LayoutComponent) child).getWidthModifier().getValue(); - } else { - childrenWidth += childMeasure.getW(); - } - } + boolean checkWeights = true; - // TODO: need to move the weight measuring in the measure function, - // currently we'll measure unnecessarily - if (hasWeights) { - float availableSpace = selfWidth - childrenWidth; + while (checkWeights) { + checkWeights = false; + boolean hasWeights = false; + float totalWeights = 0f; for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } if (child instanceof LayoutComponent && ((LayoutComponent) child).getWidthModifier().hasWeight()) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; + hasWeights = true; + totalWeights += ((LayoutComponent) child).getWidthModifier().getValue(); + } else { + childrenWidth += childMeasure.getW(); + } + } + + // TODO: need to move the weight measuring in the measure function, + // currently we'll measure unnecessarily + if (hasWeights) { + float availableSpace = selfWidth - childrenWidth; + for (Component child : mChildrenComponents) { + if (child instanceof LayoutComponent + && ((LayoutComponent) child).getWidthModifier().hasWeight()) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + float weight = ((LayoutComponent) child).getWidthModifier().getValue(); + float childWidth = (weight * availableSpace) / totalWeights; + WidthInModifierOperation widthInConstraints = + ((LayoutComponent) child).getWidthModifier().getWidthIn(); + if (widthInConstraints != null) { + float min = widthInConstraints.getMin(); + float max = widthInConstraints.getMax(); + if (min != -1) { + childWidth = Math.max(min, childWidth); + } + if (max != -1) { + childWidth = Math.min(max, childWidth); + } + } + childMeasure.setW(childWidth); + child.measure( + context, + childMeasure.getW(), + childMeasure.getW(), + childMeasure.getH(), + childMeasure.getH(), + measure); } - float weight = ((LayoutComponent) child).getWidthModifier().getValue(); - childMeasure.setW((weight * availableSpace) / totalWeights); - child.measure( - context, - childMeasure.getW(), - childMeasure.getW(), - childMeasure.getH(), - childMeasure.getH(), - measure); } } + + if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) { + checkWeights = true; + } } childrenWidth = 0f; @@ -363,6 +387,16 @@ public class RowLayout extends LayoutManager { return Operations.LAYOUT_ROW; } + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -370,7 +404,7 @@ public class RowLayout extends LayoutManager { int horizontalPositioning, int verticalPositioning, float spacedBy) { - buffer.start(Operations.LAYOUT_ROW); + buffer.start(id()); buffer.writeInt(componentId); buffer.writeInt(animationId); buffer.writeInt(horizontalPositioning); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java new file mode 100644 index 000000000000..c19bd2f6b7c0 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java @@ -0,0 +1,124 @@ +/* + * 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.layout.modifiers; + +import android.annotation.NonNull; + +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.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.DrawBase2; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Set the min / max height dimension on a component */ +public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN; + public static final String CLASS_NAME = "HeightInModifierOperation"; + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + Maker m = HeightInModifierOperation::new; + read(m, buffer, operations); + } + + /** + * Returns the min value + * + * @return minimum value + */ + public float getMin() { + return mV1; + } + + /** + * Returns the max value + * + * @return maximum value + */ + public float getMax() { + return mV2; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + @Override + protected void write(@NonNull WireBuffer buffer, float v1, float v2) { + apply(buffer, v1, v2); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "HeightInModifierOperation") + .description("Add additional constraints to the height") + .field(DocumentedOperation.FLOAT, "min", "The minimum height, -1 if not applied") + .field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied"); + } + + public HeightInModifierOperation(float min, float max) { + super(min, max); + mName = CLASS_NAME; + } + + @Override + public void paint(@NonNull PaintContext context) {} + + /** + * Writes out the HeightInModifier to the buffer + * + * @param buffer buffer to write to + * @param x1 start x of DrawOval + * @param y1 start y of the DrawOval + */ + public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { + write(buffer, OP_CODE, x1, y1); + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, "HEIGHT_IN = [" + getMin() + ", " + getMax() + "]"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java index ec078a9e73ea..4b50a916b9cd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java @@ -31,6 +31,7 @@ import java.util.List; public class HeightModifierOperation extends DimensionModifierOperation { private static final int OP_CODE = Operations.MODIFIER_HEIGHT; public static final String CLASS_NAME = "HeightModifierOperation"; + private HeightInModifierOperation mHeightIn = null; /** * The name of the class @@ -110,4 +111,22 @@ public class HeightModifierOperation extends DimensionModifierOperation { .field(INT, "type", "") .field(FLOAT, "value", ""); } + + /** + * Set height in constraints + * + * @param heightInConstraints height constraints + */ + public void setHeightIn(HeightInModifierOperation heightInConstraints) { + mHeightIn = heightInConstraints; + } + + /** + * Returns height in constraints + * + * @return height in constraints + */ + public HeightInModifierOperation getHeightIn() { + return mHeightIn; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java new file mode 100644 index 000000000000..c3624e5b3d88 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java @@ -0,0 +1,124 @@ +/* + * 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.layout.modifiers; + +import android.annotation.NonNull; + +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.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.DrawBase2; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Set the min / max width dimension on a component */ +public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN; + public static final String CLASS_NAME = "WidthInModifierOperation"; + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + Maker m = WidthInModifierOperation::new; + read(m, buffer, operations); + } + + /** + * Returns the min value + * + * @return minimum value + */ + public float getMin() { + return mV1; + } + + /** + * Returns the max value + * + * @return maximum value + */ + public float getMax() { + return mV2; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + @Override + protected void write(@NonNull WireBuffer buffer, float v1, float v2) { + apply(buffer, v1, v2); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "WidthInModifierOperation") + .description("Add additional constraints to the width") + .field(DocumentedOperation.FLOAT, "min", "The minimum width, -1 if not applied") + .field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied"); + } + + public WidthInModifierOperation(float min, float max) { + super(min, max); + mName = CLASS_NAME; + } + + @Override + public void paint(@NonNull PaintContext context) {} + + /** + * Writes out the WidthInModifier to the buffer + * + * @param buffer buffer to write to + * @param x1 start x of DrawOval + * @param y1 start y of the DrawOval + */ + public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { + write(buffer, OP_CODE, x1, y1); + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java index 05305988a49f..532027ab2087 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java @@ -31,6 +31,7 @@ import java.util.List; public class WidthModifierOperation extends DimensionModifierOperation { private static final int OP_CODE = Operations.MODIFIER_WIDTH; public static final String CLASS_NAME = "WidthModifierOperation"; + private WidthInModifierOperation mWidthIn = null; /** * The name of the class @@ -110,4 +111,22 @@ public class WidthModifierOperation extends DimensionModifierOperation { .field(INT, "type", "") .field(FLOAT, "value", ""); } + + /** + * Set width in constraints + * + * @param widthInConstraints width constraints + */ + public void setWidthIn(WidthInModifierOperation widthInConstraints) { + mWidthIn = widthInConstraints; + } + + /** + * Returns width in constraints + * + * @return width in constraints + */ + public WidthInModifierOperation getWidthIn() { + return mWidthIn; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java index b2ea0afd8fab..eb834a97c723 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java @@ -150,17 +150,47 @@ public class AnimatedFloatExpression { /** RAND_SEED operator */ public static final float RAND_SEED = asNan(OFFSET + 40); + /** NOISE_FROM operator calculate a random 0..1 number based on a seed */ + public static final float NOISE_FROM = asNan(OFFSET + 41); + + /** RANDOM_IN_RANGE random number in range */ + public static final float RAND_IN_RANGE = asNan(OFFSET + 42); + + /** SQUARE_SUM the sum of the square of two numbers */ + public static final float SQUARE_SUM = asNan(OFFSET + 43); + + /** STEP x > edge ? 1 : 0; */ + public static final float STEP = asNan(OFFSET + 44); + + /** SQUARE x*x; */ + public static final float SQUARE = asNan(OFFSET + 45); + + /** DUP x,x; */ + public static final float DUP = asNan(OFFSET + 46); + + /** HYPOT sqrt(x*x+y*y); */ + public static final float HYPOT = asNan(OFFSET + 47); + + /** SWAP y,x; */ + public static final float SWAP = asNan(OFFSET + 48); + + /** LERP (1-t)*x+t*y; */ + public static final float LERP = asNan(OFFSET + 49); + + /** SMOOTH_STEP (1-smoothstep(edge0,edge1,x)); */ + public static final float SMOOTH_STEP = asNan(OFFSET + 50); + /** LAST valid operator */ - public static final int LAST_OP = OFFSET + 40; + public static final int LAST_OP = OFFSET + 50; /** VAR1 operator */ - public static final float VAR1 = asNan(OFFSET + 41); + public static final float VAR1 = asNan(OFFSET + 51); /** VAR2 operator */ - public static final float VAR2 = asNan(OFFSET + 42); + public static final float VAR2 = asNan(OFFSET + 52); /** VAR2 operator */ - public static final float VAR3 = asNan(OFFSET + 43); + public static final float VAR3 = asNan(OFFSET + 53); // TODO SQUARE, DUP, HYPOT, SWAP // private static final float FP_PI = (float) Math.PI; @@ -399,6 +429,17 @@ public class AnimatedFloatExpression { sNames.put(k++, "RAND"); sNames.put(k++, "RAND_SEED"); + sNames.put(k++, "noise_from"); + sNames.put(k++, "rand_in_range"); + sNames.put(k++, "square_sum"); + sNames.put(k++, "step"); + sNames.put(k++, "square"); + sNames.put(k++, "dup"); + sNames.put(k++, "hypot"); + sNames.put(k++, "swap"); + sNames.put(k++, "lerp"); + sNames.put(k++, "smooth_step"); + sNames.put(k++, "a[0]"); sNames.put(k++, "a[1]"); sNames.put(k++, "a[2]"); @@ -615,9 +656,20 @@ public class AnimatedFloatExpression { private static final int OP_RAND = OFFSET + 39; private static final int OP_RAND_SEED = OFFSET + 40; - private static final int OP_FIRST_VAR = OFFSET + 41; - private static final int OP_SECOND_VAR = OFFSET + 42; - private static final int OP_THIRD_VAR = OFFSET + 43; + private static final int OP_NOISE_FROM = OFFSET + 41; + private static final int OP_RAND_IN_RANGE = OFFSET + 42; + private static final int OP_SQUARE_SUM = OFFSET + 43; + private static final int OP_STEP = OFFSET + 44; + private static final int OP_SQUARE = OFFSET + 45; + private static final int OP_DUP = OFFSET + 46; + private static final int OP_HYPOT = OFFSET + 47; + private static final int OP_SWAP = OFFSET + 48; + private static final int OP_LERP = OFFSET + 49; + private static final int OP_SMOOTH_STEP = OFFSET + 50; + + private static final int OP_FIRST_VAR = OFFSET + 51; + private static final int OP_SECOND_VAR = OFFSET + 52; + private static final int OP_THIRD_VAR = OFFSET + 53; int opEval(int sp, int id) { float[] array; @@ -824,6 +876,66 @@ public class AnimatedFloatExpression { } } return sp - 1; + case OP_NOISE_FROM: + int x = Float.floatToRawIntBits(mStack[sp]); + x = (x << 13) ^ x; // / Bitwise scrambling return + mStack[sp] = + (1.0f + - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) + / 1073741824.0f); + return sp; + + case OP_RAND_IN_RANGE: + if (sRandom == null) { + sRandom = new Random(); + } + mStack[sp] = sRandom.nextFloat() * (mStack[sp] - mStack[sp - 1]) + mStack[sp - 1]; + return sp; + case OP_SQUARE_SUM: + mStack[sp - 1] = mStack[sp - 1] * mStack[sp - 1] + mStack[sp] * mStack[sp]; + return sp - 1; + case OP_STEP: + System.out.println(mStack[sp] + " > " + mStack[sp - 1]); + mStack[sp - 1] = (mStack[sp - 1] > mStack[sp]) ? 1f : 0f; + return sp - 1; + case OP_SQUARE: + mStack[sp] = mStack[sp] * mStack[sp]; + return sp; + case OP_DUP: + mStack[sp + 1] = mStack[sp]; + return sp + 1; + case OP_HYPOT: + mStack[sp - 1] = (float) Math.hypot(mStack[sp - 1], mStack[sp]); + return sp - 1; + case OP_SWAP: + float swap = mStack[sp - 1]; + mStack[sp - 1] = mStack[sp]; + mStack[sp] = swap; + return sp; + case OP_LERP: + float tmp1 = mStack[sp - 2]; + float tmp2 = mStack[sp - 1]; + float tmp3 = mStack[sp]; + mStack[sp - 2] = tmp1 + (tmp2 - tmp1) * tmp3; + return sp - 2; + case OP_SMOOTH_STEP: + float val3 = mStack[sp - 2]; + float max2 = mStack[sp - 1]; + float min1 = mStack[sp]; + System.out.println("val3 = " + val3 + " min1 = " + min1 + " max2 = " + max2); + if (val3 < min1) { + mStack[sp - 2] = 0f; + System.out.println("below min "); + } else if (val3 > max2) { + mStack[sp - 2] = 1f; + System.out.println("above max "); + + } else { + float v = (val3 - min1) / (max2 - min1); + System.out.println("v = " + v); + mStack[sp - 2] = v * v * (3 - 2 * v); + } + return sp - 2; case OP_FIRST_VAR: mStack[sp] = mVar[0]; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java index d8bc83eb8a2e..2b5368297dae 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java @@ -19,24 +19,58 @@ package com.android.internal.widget.remotecompose.core.operations.utilities.easi public abstract class Easing { int mType; - /** get the value at point x */ + /** + * get the value at point x + * + * @param x the position at which to get the slope + * @return the value at the point + */ public abstract float get(float x); - /** get the slope of the easing function at at x */ + /** + * get the slope of the easing function at at x + * + * @param x the position at which to get the slope + * @return the slope + */ public abstract float getDiff(float x); + /** + * get the type of easing function + * + * @return the type of easing function + */ public int getType() { return mType; } + /** cubic Easing function that accelerates and decelerates */ public static final int CUBIC_STANDARD = 1; + + /** cubic Easing function that accelerates */ public static final int CUBIC_ACCELERATE = 2; + + /** cubic Easing function that decelerates */ public static final int CUBIC_DECELERATE = 3; + + /** cubic Easing function that just linearly interpolates */ public static final int CUBIC_LINEAR = 4; + + /** cubic Easing function that goes bacwards and then accelerates */ public static final int CUBIC_ANTICIPATE = 5; + + /** cubic Easing function that overshoots and then goes back */ public static final int CUBIC_OVERSHOOT = 6; + + /** cubic Easing function that you customize */ public static final int CUBIC_CUSTOM = 11; + + /** a monotonic spline Easing function that you customize */ public static final int SPLINE_CUSTOM = 12; + + /** a bouncing Easing function */ public static final int EASE_OUT_BOUNCE = 13; + + /** a elastic Easing function */ public static final int EASE_OUT_ELASTIC = 14; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java index 465c95d06726..65472c262206 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java @@ -52,16 +52,25 @@ public class FloatAnimation extends Easing { return str; } - public FloatAnimation() { - mType = CUBIC_STANDARD; - mEasingCurve = new CubicEasing(mType); - } - + /** + * Create an animation based on a float encoding of the animation + * + * @param description the float encoding of the animation + */ public FloatAnimation(@NonNull float... description) { mType = CUBIC_STANDARD; setAnimationDescription(description); } + /** + * Create an animation based on the parameters + * + * @param type The type of animation + * @param duration The duration of the animation + * @param description The float parameters describing the animation + * @param initialValue The initial value of the float (NaN if none) + * @param wrap The wrap value of the animation NaN if it does not wrap + */ public FloatAnimation( int type, float duration, @@ -139,8 +148,8 @@ public class FloatAnimation extends Easing { /** * Useful to debug the packed form of an animation string * - * @param description - * @return + * @param description the float encoding of the animation + * @return a string describing the animation */ public static String unpackAnimationToString(float[] description) { float[] mSpec = description; @@ -223,7 +232,7 @@ public class FloatAnimation extends Easing { /** * Create an animation based on a float encoding of the animation * - * @param description + * @param description the float encoding of the animation */ public void setAnimationDescription(@NonNull float[] description) { mSpec = description; @@ -288,7 +297,7 @@ public class FloatAnimation extends Easing { /** * Set the initial Value * - * @param value + * @param value the value to set */ public void setInitialValue(float value) { @@ -321,7 +330,7 @@ public class FloatAnimation extends Easing { /** * Set the target value to interpolate to * - * @param value + * @param value the value to set */ public void setTargetValue(float value) { mTargetValue = value; @@ -342,6 +351,11 @@ public class FloatAnimation extends Easing { setScaleOffset(); } + /** + * Get the target value + * + * @return the target value + */ public float getTargetValue() { return mTargetValue; } @@ -369,6 +383,11 @@ public class FloatAnimation extends Easing { return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue); } + /** + * Get the initial value + * + * @return the initial value + */ public float getInitialValue() { return mInitialValue; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java index 06969ccd1b10..960eff2e7242 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java @@ -25,13 +25,18 @@ public class GeneralEasing extends Easing { /** * Set the curve based on the float encoding of it * - * @param data + * @param data the float encoding of the curve */ public void setCurveSpecification(@NonNull float[] data) { mEasingData = data; createEngine(); } + /** + * Get the float encoding of the curve + * + * @return the float encoding of the curve + */ public @NonNull float[] getCurveSpecification() { return mEasingData; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java index f4579a24fd44..01d64dff10f9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java @@ -76,10 +76,10 @@ public class MonotonicCurveFit { } /** - * Get the position of all curves at time t + * Get the position of all curves at position t * - * @param t - * @param v + * @param t the point on the spline + * @param v the array to fill (for multiple curves) */ public void getPos(double t, @NonNull double[] v) { final int n = mT.length; @@ -136,10 +136,10 @@ public class MonotonicCurveFit { } /** - * Get the position of all curves at time t + * Get the position of all curves at position t * - * @param t - * @param v + * @param t the point on the spline + * @param v the array to fill */ public void getPos(double t, @NonNull float[] v) { final int n = mT.length; @@ -196,11 +196,11 @@ public class MonotonicCurveFit { } /** - * Get the position of the jth curve at time t + * Get the position of the jth curve at position t * - * @param t - * @param j - * @return + * @param t the position + * @param j the curve to get + * @return the position */ public double getPos(double t, int j) { final int n = mT.length; @@ -240,8 +240,8 @@ public class MonotonicCurveFit { /** * Get the slope of all the curves at position t * - * @param t - * @param v + * @param t the position + * @param v the array to fill */ public void getSlope(double t, @NonNull double[] v) { final int n = mT.length; @@ -271,9 +271,9 @@ public class MonotonicCurveFit { /** * Get the slope of the j curve at position t * - * @param t - * @param j - * @return + * @param t the position + * @param j the curve to get the value at + * @return the slope */ public double getSlope(double t, int j) { final int n = mT.length; @@ -297,6 +297,11 @@ public class MonotonicCurveFit { return 0; // should never reach here } + /** + * Get the time point used to create the curve + * + * @return the time points used to create the curve + */ public @NonNull double[] getTimePoints() { return mT; } @@ -332,7 +337,12 @@ public class MonotonicCurveFit { + h * t1; } - /** This builds a monotonic spline to be used as a wave function */ + /** + * This builds a monotonic spline to be used as a wave function + * + * @param configString the configuration string + * @return the curve + */ @NonNull public static MonotonicCurveFit buildWave(@NonNull String configString) { // done this way for efficiency diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java index 23a664336c5f..8bb7dae2fd6a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java @@ -76,6 +76,11 @@ public class MonotonicSpline { mTangent = tangent; } + /** + * Get the value point used in the interpolator. + * + * @return the value points + */ public float[] getArray() { return mY; } @@ -83,7 +88,7 @@ public class MonotonicSpline { /** * Get the position of all curves at time t * - * @param t + * @param t the position along spline * @return position at t */ public float getPos(float t) { @@ -139,7 +144,7 @@ public class MonotonicSpline { /** * Get the slope of the curve at position t * - * @param t + * @param t the position along spline * @return slope at t */ public float getSlope(float t) { @@ -167,6 +172,11 @@ public class MonotonicSpline { return v; } + /** + * Get the time points used in the interpolator. + * + * @return the time points + */ public float[] getTimePoints() { return mT; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java index 03e45031e515..2f1379b3e9fc 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java @@ -42,9 +42,9 @@ public class SpringStopEngine { private float mStopThreshold; private int mBoundaryMode = 0; - public String debug(String desc, float time) { - return null; - } + // public String debug(String desc, float time) { + // return null; + // } void log(String str) { StackTraceElement s = new Throwable().getStackTrace()[1]; @@ -53,20 +53,41 @@ public class SpringStopEngine { System.out.println(line + str); } + /** */ public SpringStopEngine() {} + /** + * get the value the sping is pulling towards + * + * @return the value the sping is pulling towards + */ public float getTargetValue() { return (float) mTargetPos; } + /** + * get the value the sping is starting from + * + * @param v the value the sping is starting from + */ public void setInitialValue(float v) { mPos = v; } + /** + * set the value the sping is pulling towards + * + * @param v the value the sping is pulling towards + */ public void setTargetValue(float v) { mTargetPos = v; } + /** + * Create a sping engine with the parameters encoded as an array of floats + * + * @param parameters the parameters to use + */ public SpringStopEngine(float[] parameters) { if (parameters[0] != 0) { throw new RuntimeException(" parameter[0] should be 0"); @@ -83,9 +104,9 @@ public class SpringStopEngine { /** * Config the spring starting conditions * - * @param currentPos - * @param target - * @param currentVelocity + * @param currentPos the current position of the spring + * @param target the target position of the spring + * @param currentVelocity the current velocity of the spring */ public void springStart(float currentPos, float target, float currentVelocity) { mTargetPos = target; @@ -115,10 +136,22 @@ public class SpringStopEngine { mLastTime = 0; } + /** + * get the velocity of the spring at a time + * + * @param time the time to get the velocity at + * @return the velocity of the spring at a time + */ public float getVelocity(float time) { return (float) mV; } + /** + * get the position of the spring at a time + * + * @param time the time to get the position at + * @return the position of the spring at a time + */ public float get(float time) { compute(time - mLastTime); mLastTime = time; @@ -128,6 +161,11 @@ public class SpringStopEngine { return (float) mPos; } + /** + * get the acceleration of the spring + * + * @return the acceleration of the spring + */ public float getAcceleration() { double k = mStiffness; double c = mDamping; @@ -135,10 +173,20 @@ public class SpringStopEngine { return (float) (-k * x - c * mV) / mMass; } + /** + * get the velocity of the spring + * + * @return the velocity of the spring + */ public float getVelocity() { return 0; } + /** + * is the spring stopped + * + * @return true if the spring is stopped + */ public boolean isStopped() { double x = (mPos - mTargetPos); double k = mStiffness; @@ -149,6 +197,11 @@ public class SpringStopEngine { return max_def <= mStopThreshold; } + /** + * increment the spring position over time dt + * + * @param dt the time to increment the spring position over + */ private void compute(double dt) { if (dt <= 0) { // Nothing to compute if there's no time difference diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java index b1eb8041b0b3..376e1e9179f4 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java @@ -26,6 +26,13 @@ public class StepCurve extends Easing { // private static final boolean DEBUG = false; @NonNull private final MonotonicCurveFit mCurveFit; + /** + * Create a step curve from a series of values + * + * @param params the series of values to ease over + * @param offset the offset into the array + * @param len the length of the array to use + */ public StepCurve(@NonNull float[] params, int offset, int len) { mCurveFit = genSpline(params, offset, len); } diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 5de11a19799d..b17e3dc82d50 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -66,6 +66,28 @@ public class RemoteComposePlayer extends FrameLayout { } /** + * @inheritDoc + */ + public void requestLayout() { + super.requestLayout(); + + if (mInner != null) { + mInner.requestLayout(); + } + } + + /** + * @inheritDoc + */ + public void invalidate() { + super.invalidate(); + + if (mInner != null) { + mInner.invalidate(); + } + } + + /** * Returns true if the document supports drag touch events * * @return true if draggable content, false otherwise diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index 970cc4a44672..334ba62636ff 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java @@ -48,7 +48,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta boolean mHasClickAreas = false; Point mActionDownPoint = new Point(0, 0); AndroidRemoteContext mARContext = new AndroidRemoteContext(); - float mDensity = 1f; + float mDensity = Float.NaN; long mStart = System.nanoTime(); long mLastFrameDelay = 1; @@ -68,24 +68,18 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta public RemoteComposeCanvas(Context context) { super(context); - if (USE_VIEW_AREA_CLICK) { - addOnAttachStateChangeListener(this); - } + addOnAttachStateChangeListener(this); } public RemoteComposeCanvas(Context context, AttributeSet attrs) { super(context, attrs); - if (USE_VIEW_AREA_CLICK) { - addOnAttachStateChangeListener(this); - } + addOnAttachStateChangeListener(this); } public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setBackgroundColor(Color.WHITE); - if (USE_VIEW_AREA_CLICK) { - addOnAttachStateChangeListener(this); - } + addOnAttachStateChangeListener(this); } public void setDebug(int value) { @@ -124,6 +118,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mChoreographer.postFrameCallback(mFrameCallback); } mDensity = getContext().getResources().getDisplayMetrics().density; + mARContext.setDensity(mDensity); if (mDocument == null) { return; } diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp index 988aea722be3..962aefc482e4 100644 --- a/core/jni/android_os_PerfettoTrace.cpp +++ b/core/jni/android_os_PerfettoTrace.cpp @@ -23,6 +23,7 @@ #include <nativehelper/scoped_local_ref.h> #include <nativehelper/scoped_primitive_array.h> #include <nativehelper/scoped_utf_chars.h> +#include <nativehelper/utils.h> #include <tracing_sdk.h> namespace android { @@ -36,30 +37,6 @@ inline static jlong toJLong(T* ptr) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr)); } -static const char* fromJavaString(JNIEnv* env, jstring jstr) { - if (!jstr) return ""; - ScopedUtfChars chars(env, jstr); - - if (!chars.c_str()) { - ALOGE("Failed extracting string"); - return ""; - } - - return chars.c_str(); -} - -static void android_os_PerfettoTrace_event(JNIEnv* env, jclass, jint type, jlong cat_ptr, - jstring name, jlong extra_ptr) { - ScopedUtfChars name_utf(env, name); - if (!name_utf.c_str()) { - ALOGE("Failed extracting string"); - } - - tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr); - tracing_perfetto::trace_event(type, category->get(), name_utf.c_str(), - toPointer<tracing_perfetto::Extra>(extra_ptr)); -} - static jlong android_os_PerfettoTrace_get_process_track_uuid() { return tracing_perfetto::get_process_track_uuid(); } @@ -70,20 +47,18 @@ static jlong android_os_PerfettoTrace_get_thread_track_uuid(jlong tid) { static void android_os_PerfettoTrace_activate_trigger(JNIEnv* env, jclass, jstring name, jint ttl_ms) { - ScopedUtfChars name_utf(env, name); - if (!name_utf.c_str()) { - ALOGE("Failed extracting string"); - return; - } - - tracing_perfetto::activate_trigger(name_utf.c_str(), static_cast<uint32_t>(ttl_ms)); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name); + tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms)); } static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag, jstring severity) { - return toJLong(new tracing_perfetto::Category(fromJavaString(env, name), - fromJavaString(env, tag), - fromJavaString(env, severity))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + ScopedUtfChars tag_chars = GET_UTF_OR_RETURN(env, tag); + ScopedUtfChars severity_chars = GET_UTF_OR_RETURN(env, severity); + + return toJLong(new tracing_perfetto::Category(name_chars.c_str(), tag_chars.c_str(), + severity_chars.c_str())); } static jlong android_os_PerfettoTraceCategory_delete() { @@ -121,8 +96,7 @@ static const JNINativeMethod gCategoryMethods[] = { }; static const JNINativeMethod gTraceMethods[] = - {{"native_event", "(IJLjava/lang/String;J)V", (void*)android_os_PerfettoTrace_event}, - {"native_get_process_track_uuid", "()J", + {{"native_get_process_track_uuid", "()J", (void*)android_os_PerfettoTrace_get_process_track_uuid}, {"native_get_thread_track_uuid", "(J)J", (void*)android_os_PerfettoTrace_get_thread_track_uuid}, @@ -132,10 +106,11 @@ static const JNINativeMethod gTraceMethods[] = int register_android_os_PerfettoTrace(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods, NELEM(gTraceMethods)); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register perfetto native methods."); res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace$Category", gCategoryMethods, NELEM(gCategoryMethods)); - LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register category native methods."); return 0; } diff --git a/core/jni/android_os_PerfettoTrackEventExtra.cpp b/core/jni/android_os_PerfettoTrackEventExtra.cpp index 9adad7bca940..b8bdc8c29199 100644 --- a/core/jni/android_os_PerfettoTrackEventExtra.cpp +++ b/core/jni/android_os_PerfettoTrackEventExtra.cpp @@ -20,6 +20,7 @@ #include <log/log.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/scoped_utf_chars.h> +#include <nativehelper/utils.h> #include <tracing_sdk.h> static constexpr ssize_t kMaxStrLen = 4096; @@ -34,32 +35,24 @@ inline static jlong toJLong(T* ptr) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr)); } -static const char* fromJavaString(JNIEnv* env, jstring jstr) { - if (!jstr) return ""; - ScopedUtfChars chars(env, jstr); - - if (!chars.c_str()) { - ALOGE("Failed extracting string"); - return ""; - } - - return chars.c_str(); -} - static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<int64_t>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<bool>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<double>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<const char*>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() { @@ -116,9 +109,11 @@ static void android_os_PerfettoTrackEventExtraArgDouble_set_value(jlong ptr, jdo static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr, jstring val) { + ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val); + tracing_perfetto::DebugArg<const char*>* arg = toPointer<tracing_perfetto::DebugArg<const char*>>(ptr); - arg->set_value(strdup(fromJavaString(env, val))); + arg->set_value(strdup(val_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() { @@ -191,9 +186,11 @@ static void android_os_PerfettoTrackEventExtraFieldDouble_set_value(jlong ptr, j static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr, jlong id, jstring val) { + ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val); + tracing_perfetto::ProtoField<const char*>* field = toPointer<tracing_perfetto::ProtoField<const char*>>(ptr); - field->set_value(id, strdup(fromJavaString(env, val))); + field->set_value(id, strdup(val_chars.c_str())); } static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr, @@ -234,7 +231,8 @@ static jlong android_os_PerfettoTrackEventExtraFlow_get_extra_ptr(jlong ptr) { static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id, jstring name, jlong parent_uuid) { - return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() { @@ -248,8 +246,9 @@ static jlong android_os_PerfettoTrackEventExtraNamedTrack_get_extra_ptr(jlong pt static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name, jlong parent_uuid) { - return toJLong( - new tracing_perfetto::RegisteredTrack(1, parent_uuid, fromJavaString(env, name), true)); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + + return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true)); } static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() { @@ -317,6 +316,15 @@ static void android_os_PerfettoTrackEventExtra_clear_args(jlong ptr) { extra->clear_extras(); } +static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr, + jstring name, jlong extra_ptr) { + ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name); + + tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr); + tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(), + toPointer<tracing_perfetto::Extra>(extra_ptr)); +} + static jlong android_os_PerfettoTrackEventExtraProto_init() { return toJLong(new tracing_perfetto::Proto()); } @@ -344,7 +352,9 @@ static const JNINativeMethod gExtraMethods[] = {{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtra_init}, {"native_delete", "()J", (void*)android_os_PerfettoTrackEventExtra_delete}, {"native_add_arg", "(JJ)V", (void*)android_os_PerfettoTrackEventExtra_add_arg}, - {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}}; + {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}, + {"native_emit", "(IJLjava/lang/String;J)V", + (void*)android_os_PerfettoTrackEventExtra_emit}}; static const JNINativeMethod gProtoMethods[] = {{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtraProto_init}, diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 3108f1f2c7c5..e78c5247d8a7 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -349,21 +349,19 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip * satisfied : * * - The entry is under the lib/ directory. - * - The entry name ends with ".so" and the entry name starts with "lib", - * an exception is made for debuggable apps. * - The entry filename is "safe" (as determined by isFilenameSafe). * */ class NativeLibrariesIterator { private: - NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie) - : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(nullptr) { + NativeLibrariesIterator(ZipFileRO* zipFile, void* cookie) + : mZipFile(zipFile), mCookie(cookie), mLastSlash(nullptr) { fileName[0] = '\0'; } public: static base::expected<std::unique_ptr<NativeLibrariesIterator>, int32_t> create( - ZipFileRO* zipFile, bool debuggable) { + ZipFileRO* zipFile) { // Do not specify a suffix to find both .so files and gdbserver. auto result = zipFile->startIterationOrError(APK_LIB.data(), nullptr /* suffix */); if (!result.ok()) { @@ -371,7 +369,7 @@ public: } return std::unique_ptr<NativeLibrariesIterator>( - new NativeLibrariesIterator(zipFile, debuggable, result.value())); + new NativeLibrariesIterator(zipFile, result.value())); } base::expected<ZipEntryRO, int32_t> next() { @@ -390,7 +388,7 @@ public: continue; } - const char* lastSlash = util::ValidLibraryPathLastSlash(fileName, false, mDebuggable); + const char* lastSlash = util::ValidLibraryPathLastSlash(fileName, false); if (lastSlash) { mLastSlash = lastSlash; break; @@ -415,20 +413,19 @@ private: char fileName[PATH_MAX]; ZipFileRO* const mZipFile; - const bool mDebuggable; void* mCookie; const char* mLastSlash; }; static install_status_t iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, - jboolean debuggable, iterFunc callFunc, void* callArg) { + iterFunc callFunc, void* callArg) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); if (zipFile == nullptr) { return INSTALL_FAILED_INVALID_APK; } - auto result = NativeLibrariesIterator::create(zipFile, debuggable); + auto result = NativeLibrariesIterator::create(zipFile); if (!result.ok()) { return INSTALL_FAILED_INVALID_APK; } @@ -470,14 +467,13 @@ iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, return INSTALL_SUCCEEDED; } -static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray, - jboolean debuggable) { +static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); if (zipFile == nullptr) { return INSTALL_FAILED_INVALID_APK; } - auto result = NativeLibrariesIterator::create(zipFile, debuggable); + auto result = NativeLibrariesIterator::create(zipFile); if (!result.ok()) { return INSTALL_FAILED_INVALID_APK; } @@ -548,26 +544,26 @@ com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, { jboolean app_compat_16kb = app_compat_16kb_enabled(); void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb }; - return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, + return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, copyFileIfChanged, reinterpret_cast<void*>(args)); } static jlong com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz, - jlong apkHandle, jstring javaCpuAbi, jboolean debuggable) + jlong apkHandle, jstring javaCpuAbi) { size_t totalSize = 0; - iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, sumFiles, &totalSize); + iterateOverNativeFiles(env, apkHandle, javaCpuAbi, sumFiles, &totalSize); return totalSize; } static jint com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz, - jlong apkHandle, jobjectArray javaCpuAbisToSearch, jboolean debuggable) + jlong apkHandle, jobjectArray javaCpuAbisToSearch) { - return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch, debuggable); + return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); } enum bitcode_scan_result_t { @@ -748,7 +744,7 @@ static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment( return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; } - auto result = NativeLibrariesIterator::create(zipFile, debuggable); + auto result = NativeLibrariesIterator::create(zipFile); if (!result.ok()) { ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName()); return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; @@ -810,9 +806,9 @@ static const JNINativeMethod gMethods[] = { {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close}, {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I", (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, - {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J", + {"nativeSumNativeBinaries", "(JLjava/lang/String;)J", (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, - {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I", + {"nativeFindSupportedAbi", "(J[Ljava/lang/String;)I", (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, {"hasRenderscriptBitcode", "(J)I", (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 0eb7c4aee287..5225ce878310 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -51,7 +51,8 @@ static const char* kPathAllowlist[] = { "/dev/blkio/tasks", "/metadata/aconfig/maps/system.package.map", "/metadata/aconfig/maps/system.flag.map", - "/metadata/aconfig/boot/system.val" + "/metadata/aconfig/boot/system.val", + "/metadata/libprocessgroup/memcg_v2_max_activation_depth" // TODO Revert after go/android-memcgv2-exp b/386797433 }; static const char kFdPath[] = "/proc/self/fd"; diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 5d0b340ac839..69c812c6fb41 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -109,6 +109,7 @@ message SecureSettingsProto { optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Settings for accessibility autoclick optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 445080215017..73279700ecb1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -180,6 +180,7 @@ <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" /> <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" /> <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" /> + <protected-broadcast android:name="android.bluetooth.device.action.ENCRYPTION_CHANGE" /> <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" /> <protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" /> <protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" /> diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml index 44a5df9b60be..c43975e4ad3c 100644 --- a/core/res/res/layout/preference_list_fragment.xml +++ b/core/res/res/layout/preference_list_fragment.xml @@ -19,7 +19,6 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:fitsSystemWindows="true" android:layout_height="match_parent" android:layout_width="match_parent" android:background="@android:color/transparent" diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml index 4df76029e606..db2fe7d038e0 100644 --- a/core/res/res/layout/preference_list_fragment_material.xml +++ b/core/res/res/layout/preference_list_fragment_material.xml @@ -19,7 +19,6 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:fitsSystemWindows="true" android:layout_height="match_parent" android:layout_width="match_parent" android:background="@android:color/transparent" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 416e0aeb776c..ec1be83dcae6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5235,10 +5235,6 @@ <!-- Whether or not swipe up gesture's opt-in setting is available on this device --> <bool name="config_swipe_up_gesture_setting_available">true</bool> - <!-- Applications which are disabled unless matching a particular sku --> - <string-array name="config_disableApksUnlessMatchedSku_apk_list" translatable="false" /> - <string-array name="config_disableApkUnlessMatchedSku_skus_list" translatable="false" /> - <!-- Whether or not we should show the option to show battery percentage --> <bool name="config_battery_percentage_setting_available">true</bool> @@ -7323,4 +7319,8 @@ <!-- Whether the device supports Wi-Fi USD feature. --> <bool name="config_deviceSupportsWifiUsd">false</bool> + + <!-- Array containing the notification assistant service adjustments that are not supported by + default on this device--> + <string-array translatable="false" name="config_notificationDefaultUnsupportedAdjustments" /> </resources> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 666f1cf39fe3..965c69d16d79 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -84,7 +84,7 @@ CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY. If 0, the device always switch to the higher score SIM. If < 0, the network type and signal strength based auto switch is disabled. --> - <integer name="auto_data_switch_score_tolerance">-1</integer> + <integer name="auto_data_switch_score_tolerance">4000</integer> <java-symbol type="integer" name="auto_data_switch_score_tolerance" /> <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 3f657541eb28..7baaa6d590f2 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -116,7 +116,7 @@ <public name="adServiceTypes" /> <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> <public name="languageSettingsActivity"/> - <!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") --> + <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> <public name="dreamCategory"/> <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") @hide @SystemApi --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 84d51f0b8ad8..77cc6868bd58 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4366,10 +4366,6 @@ <java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" /> <java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" /> - - <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> - <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" /> - <java-symbol type="string" name="config_misprovisionedDeviceModel" /> <java-symbol type="string" name="config_misprovisionedBrandValue" /> @@ -5842,4 +5838,6 @@ <!-- Whether the device supports Wi-Fi USD feature. --> <java-symbol type="bool" name="config_deviceSupportsWifiUsd" /> + <java-symbol type="array" name="config_notificationDefaultUnsupportedAdjustments" /> + </resources> diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java index 44b2d90decf2..dfe7d0306905 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -26,7 +26,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Parcel; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; @@ -124,7 +123,6 @@ public class InputMethodInfoTest { } @Test - @EnableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) public void testIsVirtualDeviceOnly() throws Exception { final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only); diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java index 292f7500479b..ad28383689af 100644 --- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java +++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java @@ -112,15 +112,14 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(FOO_CATEGORY, "event") .addFlow(2) .addTerminatingFlow(3) .addArg("long_val", 10000000000L) .addArg("bool_val", true) .addArg("double_val", 3.14) .addArg("string_val", FOO) - .build(); - PerfettoTrace.instant(FOO_CATEGORY, "event", extra); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -163,12 +162,12 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) - public void testDebugAnnotationsWithLamda() throws Exception { + public void testDebugAnnotationsWithLambda() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrace.instant(FOO_CATEGORY, "event", e -> e.addArg("long_val", 123L)); + PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -203,15 +202,14 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra beginExtra = PerfettoTrackEventExtra.builder() - .usingNamedTrack(FOO, PerfettoTrace.getProcessTrackUuid()) - .build(); - PerfettoTrace.begin(FOO_CATEGORY, "event", beginExtra); + PerfettoTrace.begin(FOO_CATEGORY, "event") + .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO) + .emit(); - PerfettoTrackEventExtra endExtra = PerfettoTrackEventExtra.builder() - .usingNamedTrack("bar", PerfettoTrace.getThreadTrackUuid(Process.myTid())) - .build(); - PerfettoTrace.end(FOO_CATEGORY, endExtra); + + PerfettoTrace.end(FOO_CATEGORY) + .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar") + .emit(); Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); @@ -242,26 +240,67 @@ public class PerfettoTraceTest { assertThat(hasTrackUuid).isTrue(); assertThat(mCategoryNames).contains(FOO); assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar"); } @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) - public void testCounter() throws Exception { + public void testProcessThreadNamedTrack() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra intExtra = PerfettoTrackEventExtra.builder() - .usingCounterTrack(FOO, PerfettoTrace.getProcessTrackUuid()) - .setCounter(16) - .build(); - PerfettoTrace.counter(FOO_CATEGORY, intExtra); + PerfettoTrace.begin(FOO_CATEGORY, "event") + .usingProcessNamedTrack(FOO) + .emit(); - PerfettoTrackEventExtra doubleExtra = PerfettoTrackEventExtra.builder() - .usingCounterTrack("bar", PerfettoTrace.getProcessTrackUuid()) - .setCounter(3.14) - .build(); - PerfettoTrace.counter(FOO_CATEGORY, doubleExtra); + + PerfettoTrace.end(FOO_CATEGORY) + .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool") + .emit(); + + Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + + boolean hasTrackEvent = false; + boolean hasTrackUuid = false; + for (TracePacket packet: trace.getPacketList()) { + TrackEvent event; + if (packet.hasTrackEvent()) { + hasTrackEvent = true; + event = packet.getTrackEvent(); + + if (TrackEvent.Type.TYPE_SLICE_BEGIN.equals(event.getType()) + && event.hasTrackUuid()) { + hasTrackUuid = true; + } + + if (TrackEvent.Type.TYPE_SLICE_END.equals(event.getType()) + && event.hasTrackUuid()) { + hasTrackUuid &= true; + } + } + + collectInternedData(packet); + collectTrackNames(packet); + } + + assertThat(hasTrackEvent).isTrue(); + assertThat(hasTrackUuid).isTrue(); + assertThat(mCategoryNames).contains(FOO); + assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar-stool"); + } + + @Test + @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + public void testCounterSimple() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + long ptr = nativeStartTracing(traceConfig.toByteArray()); + + PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit(); + + PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit(); Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); @@ -297,12 +336,102 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + public void testCounter() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + long ptr = nativeStartTracing(traceConfig.toByteArray()); + + PerfettoTrace.counter(FOO_CATEGORY, 16) + .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit(); + + PerfettoTrace.counter(FOO_CATEGORY, 3.14) + .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), + "%s-%s", "bar", "stool").emit(); + + Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + + boolean hasTrackEvent = false; + boolean hasCounterValue = false; + boolean hasDoubleCounterValue = false; + for (TracePacket packet: trace.getPacketList()) { + TrackEvent event; + if (packet.hasTrackEvent()) { + hasTrackEvent = true; + event = packet.getTrackEvent(); + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getCounterValue() == 16) { + hasCounterValue = true; + } + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getDoubleCounterValue() == 3.14) { + hasDoubleCounterValue = true; + } + } + + collectTrackNames(packet); + } + + assertThat(hasTrackEvent).isTrue(); + assertThat(hasCounterValue).isTrue(); + assertThat(hasDoubleCounterValue).isTrue(); + assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar-stool"); + } + + @Test + @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + public void testProcessThreadCounter() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + long ptr = nativeStartTracing(traceConfig.toByteArray()); + + PerfettoTrace.counter(FOO_CATEGORY, 16).usingProcessCounterTrack(FOO).emit(); + + PerfettoTrace.counter(FOO_CATEGORY, 3.14) + .usingThreadCounterTrack(Process.myTid(), "%s-%s", "bar", "stool").emit(); + + Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + + boolean hasTrackEvent = false; + boolean hasCounterValue = false; + boolean hasDoubleCounterValue = false; + for (TracePacket packet: trace.getPacketList()) { + TrackEvent event; + if (packet.hasTrackEvent()) { + hasTrackEvent = true; + event = packet.getTrackEvent(); + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getCounterValue() == 16) { + hasCounterValue = true; + } + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getDoubleCounterValue() == 3.14) { + hasDoubleCounterValue = true; + } + } + + collectTrackNames(packet); + } + + assertThat(hasTrackEvent).isTrue(); + assertThat(hasCounterValue).isTrue(); + assertThat(hasDoubleCounterValue).isTrue(); + assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar-stool"); + } + + @Test + @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) public void testProto() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra5 = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(FOO_CATEGORY, "event_proto") .beginProto() .beginNested(33L) .addField(4L, 2L) @@ -310,8 +439,7 @@ public class PerfettoTraceTest { .endNested() .addField(2001, "AIDL::IActivityManager") .endProto() - .build(); - PerfettoTrace.instant(FOO_CATEGORY, "event_proto", extra5); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -351,7 +479,7 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra6 = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested") .beginProto() .beginNested(29L) .beginNested(4L) @@ -364,8 +492,7 @@ public class PerfettoTraceTest { .endNested() .endNested() .endProto() - .build(); - PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested", extra6); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -413,8 +540,7 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder().build(); - PerfettoTrace.instant(FOO_CATEGORY, "event_trigger", extra); + PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit(); PerfettoTrace.activateTrigger(FOO, 1000); @@ -439,49 +565,21 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) - public void testMultipleExtras() throws Exception { - boolean hasException = false; - try { - PerfettoTrackEventExtra.builder(); - - // Unclosed extra will throw an exception here - PerfettoTrackEventExtra.builder(); - } catch (Exception e) { - hasException = true; - } - - try { - PerfettoTrackEventExtra.builder().build(); - - // Closed extra but unused (reset hasn't been called internally) will throw an exception - // here. - PerfettoTrackEventExtra.builder(); - } catch (Exception e) { - hasException &= true; - } - - assertThat(hasException).isTrue(); - } - - @Test - @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) public void testRegister() throws Exception { TraceConfig traceConfig = getTraceConfig(BAR); Category barCategory = new Category(BAR); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra beforeExtra = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(barCategory, "event") .addArg("before", 1) - .build(); - PerfettoTrace.instant(barCategory, "event", beforeExtra); + .emit(); barCategory.register(); - PerfettoTrackEventExtra afterExtra = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(barCategory, "event") .addArg("after", 1) - .build(); - PerfettoTrace.instant(barCategory, "event", afterExtra); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index f87b6994900f..ee8d428d8370 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -224,7 +224,7 @@ public class ContentCaptureSessionTest { } @Override - void flush(int reason) { + void internalFlush(int reason) { throw new UnsupportedOperationException("should not have been called"); } diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index b42bcee77c67..a1d7f87614e4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -263,7 +263,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -280,7 +280,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -298,7 +298,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -316,7 +316,7 @@ public class MainContentCaptureSessionTest { session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); session.mDirectServiceInterface = mMockContentCaptureDirectManager; - session.flush(REASON); + session.internalFlush(REASON); mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -499,6 +499,57 @@ public class MainContentCaptureSessionTest { assertThat(session.mEventProcessQueue).hasSize(1); } + @Test + public void notifyContentCaptureEvents_beforeSessionPerformStart() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mContentCaptureHandler = null; + session.mDirectServiceInterface = null; + + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + assertThat(session.mEvents).isNull(); + assertThat(session.mEventProcessQueue).hasSize(7); // 5 view events + 2 view tree events + } + + @Test + public void notifyViewAppeared_beforeSessionPerformStart() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mContentCaptureHandler = null; + session.mDirectServiceInterface = null; + + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + + assertThat(session.mEvents).isNull(); + assertThat(session.mEventProcessQueue).hasSize(1); + } + + @Test + public void flush_beforeSessionPerformStart() throws Exception { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mEvents = new ArrayList<>(Arrays.asList(EVENT)); + session.mContentCaptureHandler = null; + session.mDirectServiceInterface = null; + + session.internalFlush(REASON); + + assertThat(session.mEvents).hasSize(1); + assertThat(session.mEventProcessQueue).isEmpty(); + } + /** Simulates the regular content capture events sequence. */ private void notifyContentCaptureEvents(final MainContentCaptureSession session) { final ArrayList<Object> events = new ArrayList<>( @@ -561,8 +612,8 @@ public class MainContentCaptureSessionTest { sStrippedContext, manager, testHandler, - testHandler, mMockSystemServerInterface); + session.mContentCaptureHandler = testHandler; session.mComponentName = COMPONENT_NAME; return session; } diff --git a/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java new file mode 100644 index 000000000000..53407f0ef5b8 --- /dev/null +++ b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2025 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.util.proto; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + + +/** + * Unit tests for {@link android.util.proto.ProtoFieldFilter}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:ProtoFieldFilterTest + * + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ProtoFieldFilterTest { + + private static final class FieldTypes { + static final long INT64 = ProtoStream.FIELD_TYPE_INT64 | ProtoStream.FIELD_COUNT_SINGLE; + static final long FIXED64 = ProtoStream.FIELD_TYPE_FIXED64 | ProtoStream.FIELD_COUNT_SINGLE; + static final long BYTES = ProtoStream.FIELD_TYPE_BYTES | ProtoStream.FIELD_COUNT_SINGLE; + static final long FIXED32 = ProtoStream.FIELD_TYPE_FIXED32 | ProtoStream.FIELD_COUNT_SINGLE; + static final long MESSAGE = ProtoStream.FIELD_TYPE_MESSAGE | ProtoStream.FIELD_COUNT_SINGLE; + static final long INT32 = ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_SINGLE; + } + + private ProtoOutputStream createBasicTestProto() { + ProtoOutputStream out = new ProtoOutputStream(); + + out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L); + out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL); + out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{1, 2, 3, 4, 5}); + out.writeFixed32(ProtoStream.makeFieldId(4, FieldTypes.FIXED32), 0xDEADBEEF); + + return out; + } + + private byte[] filterProto(byte[] input, ProtoFieldFilter filter) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(input); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + filter.filter(inputStream, outputStream); + return outputStream.toByteArray(); + } + + @Test + public void testNoFieldsFiltered() throws IOException { + byte[] input = createBasicTestProto().getBytes(); + byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true)); + assertArrayEquals("No fields should be filtered out", input, output); + } + + @Test + public void testAllFieldsFiltered() throws IOException { + byte[] input = createBasicTestProto().getBytes(); + byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> false)); + + assertEquals("All fields should be filtered out", 0, output.length); + } + + @Test + public void testSpecificFieldsFiltered() throws IOException { + + ProtoOutputStream out = createBasicTestProto(); + byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2)); + + ProtoInputStream in = new ProtoInputStream(output); + boolean[] fieldsFound = new boolean[5]; + + int fieldNumber; + while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) { + fieldsFound[fieldNumber] = true; + switch (fieldNumber) { + case 1: + assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64))); + break; + case 2: + fail("Field 2 should be filtered out"); + break; + case 3: + assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, + in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES))); + break; + case 4: + assertEquals(0xDEADBEEF, + in.readInt(ProtoStream.makeFieldId(4, FieldTypes.FIXED32))); + break; + default: + fail("Unexpected field number: " + fieldNumber); + } + } + + assertTrue("Field 1 should be present", fieldsFound[1]); + assertFalse("Field 2 should be filtered", fieldsFound[2]); + assertTrue("Field 3 should be present", fieldsFound[3]); + assertTrue("Field 4 should be present", fieldsFound[4]); + } + + @Test + public void testDifferentWireTypes() throws IOException { + ProtoOutputStream out = new ProtoOutputStream(); + + out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L); + out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL); + out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{10, 20, 30}); + + long token = out.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE)); + out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 42); + out.end(token); + + out.writeFixed32(ProtoStream.makeFieldId(5, FieldTypes.FIXED32), 0xDEADBEEF); + + byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(fieldNumber -> true)); + + ProtoInputStream in = new ProtoInputStream(output); + boolean[] fieldsFound = new boolean[6]; + + int fieldNumber; + while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) { + fieldsFound[fieldNumber] = true; + switch (fieldNumber) { + case 1: + assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64))); + break; + case 2: + assertEquals(0x1234567890ABCDEFL, + in.readLong(ProtoStream.makeFieldId(2, FieldTypes.FIXED64))); + break; + case 3: + assertArrayEquals(new byte[]{10, 20, 30}, + in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES))); + break; + case 4: + token = in.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE)); + assertTrue(in.nextField() == 1); + assertEquals(42, in.readInt(ProtoStream.makeFieldId(1, FieldTypes.INT32))); + assertTrue(in.nextField() == ProtoInputStream.NO_MORE_FIELDS); + in.end(token); + break; + case 5: + assertEquals(0xDEADBEEF, + in.readInt(ProtoStream.makeFieldId(5, FieldTypes.FIXED32))); + break; + default: + fail("Unexpected field number: " + fieldNumber); + } + } + + assertTrue("All fields should be present", + fieldsFound[1] && fieldsFound[2] && fieldsFound[3] + && fieldsFound[4] && fieldsFound[5]); + } + @Test + public void testNestedMessagesUnfiltered() throws IOException { + ProtoOutputStream out = new ProtoOutputStream(); + + out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L); + + long token = out.start(ProtoStream.makeFieldId(2, FieldTypes.MESSAGE)); + out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 6789); + out.writeFixed32(ProtoStream.makeFieldId(2, FieldTypes.FIXED32), 0xCAFEBABE); + out.end(token); + + byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2)); + + // Verify output + ProtoInputStream in = new ProtoInputStream(output); + boolean[] fieldsFound = new boolean[3]; + + int fieldNumber; + while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) { + fieldsFound[fieldNumber] = true; + if (fieldNumber == 1) { + assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64))); + } else { + fail("Unexpected field number: " + fieldNumber); + } + } + + assertTrue("Field 1 should be present", fieldsFound[1]); + assertFalse("Field 2 should be filtered out", fieldsFound[2]); + } + + @Test + public void testRepeatedFields() throws IOException { + + ProtoOutputStream out = new ProtoOutputStream(); + long fieldId = ProtoStream.makeFieldId(1, + ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_REPEATED); + + out.writeRepeatedInt32(fieldId, 100); + out.writeRepeatedInt32(fieldId, 200); + out.writeRepeatedInt32(fieldId, 300); + + byte[] input = out.getBytes(); + + byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true)); + + assertArrayEquals("Repeated fields should be preserved", input, output); + } + +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a30570a4cce5..b8059d08756a 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -613,6 +613,10 @@ applications that come with the platform <permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" /> <!-- Permission required for CTS test - CtsContentProviderMultiUserTest --> <permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/> + <!-- Permission required for CTS test - MediaQualityTest --> + <permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/> + <permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/> + <permission name="android.permission.READ_COLOR_ZONES"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 906c71d9caca..1c34e0d54908 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -66,12 +66,6 @@ public final class BLASTBufferQueue { } /** Create a new connection with the surface flinger. */ - public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, - @PixelFormat.Format int format) { - this(name, true /* updateDestinationFrame */); - update(sc, width, height, format); - } - public BLASTBufferQueue(String name, boolean updateDestinationFrame) { mNativeObject = nativeCreate(name, updateDestinationFrame); } diff --git a/graphics/java/android/graphics/OWNERS b/graphics/java/android/graphics/OWNERS index ef8d26cc65b9..1ea197658f93 100644 --- a/graphics/java/android/graphics/OWNERS +++ b/graphics/java/android/graphics/OWNERS @@ -2,10 +2,10 @@ romainguy@google.com jreck@google.com -njawad@google.com sumir@google.com djsollen@google.com -scroggo@google.com +alecmouri@google.com +sallyqi@google.com per-file BLASTBufferQueue.java = file:/services/core/java/com/android/server/wm/OWNERS per-file FontFamily.java = file:fonts/OWNERS diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt index 5629a1719739..dd387b382dc6 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt @@ -34,7 +34,6 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.Flags import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.properties.ProdBubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository @@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.bubbles.BubbleBarLocation diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index ab2e552c7a3d..a7eebd6159e4 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -36,10 +36,10 @@ import com.android.internal.protolog.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.Flags import com.android.wm.shell.R -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.common.FloatingContentCoordinator +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt index a9996adcbdb4..f1ba0423b422 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt @@ -35,7 +35,6 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController @@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt index bcaa63bfad36..750178678785 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt @@ -22,9 +22,9 @@ import android.content.res.Resources import android.view.LayoutInflater import com.android.internal.logging.testing.UiEventLoggerFake import com.android.wm.shell.R -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView +import com.android.wm.shell.common.TestShellExecutor import com.google.common.util.concurrent.MoreExecutors.directExecutor /** Helper to create a [Bubble] instance */ diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt index be7636787301..3b1d0a9a5106 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt @@ -36,7 +36,6 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger @@ -46,6 +45,7 @@ import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.DeviceConfig import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.FakeBubbleFactory +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt index d3a1f888ac8b..2e1d1e9b8a05 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -33,7 +33,6 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger @@ -44,6 +43,7 @@ import com.android.wm.shell.bubbles.DeviceConfig import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.RegionSamplingProvider import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.handles.RegionSamplingHelper import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 5111f289a592..a6492476176b 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -36,7 +36,6 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.bubbles.BubbleData @@ -58,6 +57,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl +import com.android.wm.shell.common.TestShellExecutor import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt index ef8e71c2590b..6b549b42cdcd 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt @@ -14,9 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell - -import com.android.wm.shell.common.ShellExecutor +package com.android.wm.shell.common /** * Simple implementation of [ShellExecutor] that collects all runnables and executes them diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml index e5fe1b5431eb..83a3959ee2f9 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml @@ -22,5 +22,6 @@ android:viewportWidth="960"> <path android:fillColor="@android:color/black" - android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/> + android:pathData="M 244.79 796.408 C 222.79 796.408 203.79 788.74 187.79 773.408 C 172.457 757.408 164.79 738.408 164.79 716.408 L 164.79 236.408 C 164.79 214.408 172.457 195.741 187.79 180.408 C 203.79 164.408 222.79 156.408 244.79 156.408 L 724.79 156.408 C 746.79 156.408 765.458 164.408 780.79 180.408 C 796.79 195.741 804.79 214.408 804.79 236.408 L 804.79 716.408 C 804.79 738.408 796.79 757.408 780.79 773.408 C 765.458 788.74 746.79 796.408 724.79 796.408 Z M 244.79 716.408 L 724.79 716.408 L 724.79 236.408 L 244.79 236.408 Z M 244.79 236.408 L 244.79 716.408 Z" + /> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp index c3ee0f76f550..0974930adfa2 100644 --- a/libs/WindowManager/Shell/shared/Android.bp +++ b/libs/WindowManager/Shell/shared/Android.bp @@ -56,6 +56,7 @@ android_library { static_libs: [ "androidx.core_core-animation", "androidx.dynamicanimation_dynamicanimation", + "com_android_wm_shell_flags_lib", "jsr330", ], kotlincflags: ["-Xjvm-default=all"], @@ -77,3 +78,17 @@ java_library { "com.android.window.flags.window-aconfig-java", ], } + +// Things that can be shared with launcher3 +java_library { + name: "WindowManager-Shell-shared-AOSP", + + sdk_version: "current", + + srcs: [ + "src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java", + ], + static_libs: [ + "com_android_wm_shell_flags_lib", + ], +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index e033f673d07d..840de2c86f92 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -40,7 +40,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; -import android.app.TaskInfo; import android.app.WindowConfiguration; import android.graphics.Rect; import android.util.ArrayMap; @@ -57,6 +56,9 @@ public class TransitionUtil { /** Flag applied to a transition change to identify it as a divider bar for animation. */ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; + /** Flag applied to a transition change to identify it as a desktop wallpaper activity. */ + public static final int FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY = FLAG_FIRST_CUSTOM << 1; + /** @return true if the transition was triggered by opening something vs closing something */ public static boolean isOpeningType(@WindowManager.TransitionType int type) { return type == TRANSIT_OPEN diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java new file mode 100644 index 000000000000..e1f1d0c32eb0 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 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.bubbles; + +import com.android.wm.shell.Flags; + +/** + * Bubble anything has some dependent flags, this class simplifies the checks. + * (TODO: b/389737359 - remove this when the feature is launched). + */ +public class BubbleAnythingFlagHelper { + + private BubbleAnythingFlagHelper() {} + + /** Whether creating any bubble or the overall bubble anything feature is enabled. */ + public static boolean enableCreateAnyBubble() { + return enableBubbleAnything() || Flags.enableCreateAnyBubble(); + } + + /** + * Whether creating any bubble and transforming to fullscreen, or the overall bubble anything + * feature is enabled. + */ + public static boolean enableBubbleToFullscreen() { + return enableBubbleAnything() + || (Flags.enableBubbleToFullscreen() + && Flags.enableCreateAnyBubble()); + } + + /** Whether the overall bubble anything feature is enabled. */ + public static boolean enableBubbleAnything() { + return Flags.enableBubbleAnything(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt index 67592e60e954..0d0bc9be72b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.common import android.app.PendingIntent +import android.app.TaskInfo import android.content.ComponentName import android.content.Intent import com.android.wm.shell.ShellTaskOrganizer @@ -34,7 +35,11 @@ object ComponentUtils { /** Retrieves the package name from a [taskId]. */ @JvmStatic fun getPackageName(taskId: Int, taskOrganizer: ShellTaskOrganizer): String? { - val taskInfo = taskOrganizer.getRunningTaskInfo(taskId) - return getPackageName(taskInfo?.baseIntent) + val taskInfo = taskOrganizer.getRunningTaskInfo(taskId) ?: return null + return getPackageName(taskInfo) } + + /** Retrieves the package name from a [TaskInfo]. */ + @JvmStatic + fun getPackageName(taskInfo: TaskInfo): String? = getPackageName(taskInfo.baseIntent) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt new file mode 100644 index 000000000000..a13ad20f8c05 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt @@ -0,0 +1,86 @@ +/* + * 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.common + +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF + +/** + * Utility class for calculating bounds during multi-display drag operations. + * + * This class provides helper functions to perform bounds calculation during window drag. + */ +object MultiDisplayDragMoveBoundsCalculator { + /** + * Calculates the global DP bounds of a window being dragged across displays. + * + * @param startDisplayLayout The DisplayLayout object of the display where the drag started. + * @param repositionStartPoint The starting position of the drag (in pixels), relative to the + * display where the drag started. + * @param boundsAtDragStart The initial bounds of the window (in pixels), relative to the + * display where the drag started. + * @param currentDisplayLayout The DisplayLayout object of the display where the pointer is + * currently located. + * @param x The current x-coordinate of the drag pointer (in pixels). + * @param y The current y-coordinate of the drag pointer (in pixels). + * @return A RectF object representing the calculated global DP bounds of the window. + */ + fun calculateGlobalDpBoundsForDrag( + startDisplayLayout: DisplayLayout, + repositionStartPoint: PointF, + boundsAtDragStart: Rect, + currentDisplayLayout: DisplayLayout, + x: Float, + y: Float, + ): RectF { + // Convert all pixel values to DP. + val startCursorDp = + startDisplayLayout.localPxToGlobalDp(repositionStartPoint.x, repositionStartPoint.y) + val currentCursorDp = currentDisplayLayout.localPxToGlobalDp(x, y) + val startLeftTopDp = + startDisplayLayout.localPxToGlobalDp(boundsAtDragStart.left, boundsAtDragStart.top) + val widthDp = startDisplayLayout.pxToDp(boundsAtDragStart.width()) + val heightDp = startDisplayLayout.pxToDp(boundsAtDragStart.height()) + + // Calculate DP bounds based on pointer movement delta. + val currentLeftDp = startLeftTopDp.x + (currentCursorDp.x - startCursorDp.x) + val currentTopDp = startLeftTopDp.y + (currentCursorDp.y - startCursorDp.y) + val currentRightDp = currentLeftDp + widthDp + val currentBottomDp = currentTopDp + heightDp + + return RectF(currentLeftDp, currentTopDp, currentRightDp, currentBottomDp) + } + + /** + * Converts global DP bounds to local pixel bounds for a specific display. + * + * @param rectDp The global DP bounds to convert. + * @param displayLayout The DisplayLayout representing the display to convert the bounds to. + * @return A Rect object representing the local pixel bounds on the specified display. + */ + fun convertGlobalDpToLocalPxForRect(rectDp: RectF, displayLayout: DisplayLayout): Rect { + val leftTopPxDisplay = displayLayout.globalDpToLocalPx(rectDp.left, rectDp.top) + val rightBottomPxDisplay = displayLayout.globalDpToLocalPx(rectDp.right, rectDp.bottom) + return Rect( + leftTopPxDisplay.x.toInt(), + leftTopPxDisplay.y.toInt(), + rightBottomPxDisplay.x.toInt(), + rightBottomPxDisplay.y.toInt(), + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java index bcd40a9a9765..c4696d5f44f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java @@ -192,15 +192,22 @@ public final class SyncTransactionQueue { throw new IllegalStateException("Sync Transactions must be serialized. In Flight: " + mInFlight.mId + " - " + mInFlight.mWCT); } - mInFlight = this; if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT); - if (mLegacyTransition != null) { - mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(), - mLegacyTransition.getAdapter(), this, mWCT); - } else { - mId = new WindowOrganizer().applySyncTransaction(mWCT, this); + try { + if (mLegacyTransition != null) { + mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(), + mLegacyTransition.getAdapter(), this, mWCT); + } else { + mId = new WindowOrganizer().applySyncTransaction(mWCT, this); + } + } catch (RuntimeException e) { + Slog.e(TAG, "Send failed", e); + // Finish current sync callback immediately. + onTransactionReady(mId, new SurfaceControl.Transaction()); + return; } if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId); + mInFlight = this; mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl index e77987963b48..37779077f9b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl @@ -16,6 +16,7 @@ package com.android.wm.shell.common.pip; +import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.view.SurfaceControl; import android.content.ComponentName; @@ -41,9 +42,8 @@ interface IPip { bounds * @return destination bounds the PiP window should land into */ - Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo, - in PictureInPictureParams pictureInPictureParams, - int launcherRotation, in Rect hotseatKeepClearArea) = 1; + Rect startSwipePipToHome(in ActivityManager.RunningTaskInfo taskInfo, int launcherRotation, + in Rect hotseatKeepClearArea) = 1; /** * Notifies the swiping Activity to PiP onto home transition is finished 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 d1ec1eb7fdf1..999c879051e3 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 @@ -1157,9 +1157,12 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, Transitions transitions, - DesktopModeEventLogger desktopModeEventLogger) { + DesktopModeEventLogger desktopModeEventLogger, + Optional<DesktopTasksLimiter> desktopTasksLimiter, + ShellTaskOrganizer shellTaskOrganizer) { return new DesktopModeLoggerTransitionObserver( - context, shellInit, transitions, desktopModeEventLogger); + context, shellInit, transitions, desktopModeEventLogger, + desktopTasksLimiter, shellTaskOrganizer); } @WMSingleton 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 c8d0dab39837..793bdf0b5614 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 @@ -83,13 +83,14 @@ public abstract class Pip2Module { @NonNull PipTransitionState pipStackListenerController, @NonNull PipDisplayLayoutState pipDisplayLayoutState, @NonNull PipUiStateChangeController pipUiStateChangeController, + DisplayController displayController, Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, Optional<DesktopWallpaperActivityTokenProvider> desktopWallpaperActivityTokenProviderOptional) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener, pipScheduler, pipStackListenerController, pipDisplayLayoutState, - pipUiStateChangeController, desktopUserRepositoriesOptional, + pipUiStateChangeController, displayController, desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index e8f9a789bb98..68bdbd1758b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -467,9 +467,13 @@ class DesktopModeEventLogger { FrameworkStatsLog .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT ), - MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value + MINIMIZE_BUTTON( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON ), + KEY_GESTURE( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_KEY_GESTURE + ), } // Default value used when the task was not unminimized. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 1ddb834399cb..9334898fdb93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -30,6 +30,7 @@ import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread @@ -125,7 +126,9 @@ class DesktopModeKeyGestureHandler( KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> { logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled") getGloballyFocusedFreeformTask()?.let { - mainExecutor.execute { desktopTasksController.get().minimizeTask(it) } + mainExecutor.execute { + desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE) + } } return true } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index c09504ee3725..2dd89c790b58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -36,6 +36,7 @@ import androidx.core.util.isNotEmpty import androidx.core.util.plus import androidx.core.util.putAll import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason @@ -52,6 +53,8 @@ import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import java.util.Optional +import kotlin.jvm.optionals.getOrNull /** * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log @@ -63,6 +66,8 @@ class DesktopModeLoggerTransitionObserver( shellInit: ShellInit, private val transitions: Transitions, private val desktopModeEventLogger: DesktopModeEventLogger, + private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, + private val shellTaskOrganizer: ShellTaskOrganizer, ) : Transitions.TransitionObserver { init { @@ -141,6 +146,7 @@ class DesktopModeLoggerTransitionObserver( // identify if we need to log any changes and update the state of visible freeform tasks identifyLogEventAndUpdateState( + transition = transition, transitionInfo = info, preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks, @@ -227,6 +233,7 @@ class DesktopModeLoggerTransitionObserver( * state and update it */ private fun identifyLogEventAndUpdateState( + transition: IBinder, transitionInfo: TransitionInfo, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, @@ -238,6 +245,7 @@ class DesktopModeLoggerTransitionObserver( ) { // Sessions is finishing, log task updates followed by an exit event identifyAndLogTaskUpdates( + transition, transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, @@ -255,6 +263,7 @@ class DesktopModeLoggerTransitionObserver( desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo)) identifyAndLogTaskUpdates( + transition, transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, @@ -262,6 +271,7 @@ class DesktopModeLoggerTransitionObserver( } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any identifyAndLogTaskUpdates( + transition, transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, @@ -275,6 +285,7 @@ class DesktopModeLoggerTransitionObserver( /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( + transition: IBinder, transitionInfo: TransitionInfo, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, @@ -310,12 +321,9 @@ class DesktopModeLoggerTransitionObserver( // find old tasks that were removed preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { - val minimizeReason = - if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) { - MinimizeReason.MINIMIZE_BUTTON - } else { - null - } + // The task is no longer visible, it might have been minimized, get the minimize + // reason (if any) + val minimizeReason = getMinimizeReason(transition, transitionInfo, taskInfo) val taskUpdate = buildTaskUpdateForTask( taskInfo, @@ -336,6 +344,21 @@ class DesktopModeLoggerTransitionObserver( } } + private fun getMinimizeReason( + transition: IBinder, + transitionInfo: TransitionInfo, + taskInfo: TaskInfo, + ): MinimizeReason? { + if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) { + return MinimizeReason.MINIMIZE_BUTTON + } + val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition) + if (minimizingTask?.taskId == taskInfo.taskId) { + return minimizingTask.minimizeReason + } + return null + } + private fun buildTaskUpdateForTask( taskInfo: TaskInfo, visibleTasks: Int, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index c975533abf24..fa696682de28 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -24,8 +24,8 @@ import android.util.SparseArray import android.view.Display.INVALID_DISPLAY import android.window.DesktopModeFlags import androidx.core.util.forEach -import androidx.core.util.keyIterator import androidx.core.util.valueIterator +import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository @@ -43,26 +43,36 @@ class DesktopRepository( @ShellMainThread private val mainCoroutineScope: CoroutineScope, val userId: Int, ) { + /** A display that supports desktops. */ + private data class DesktopDisplay( + val displayId: Int, + val orderedDesks: MutableSet<Desk> = mutableSetOf(), + // TODO: b/389960283 - update on desk activation / deactivation. + var activeDeskId: Int? = null, + ) + /** - * Task data tracked per desktop. + * Task data tracked per desk. * - * @property activeTasks task ids of active tasks currently or previously visible in Desktop - * mode session. Tasks become inactive when task closes or when desktop mode session ends. + * @property activeTasks task ids of active tasks currently or previously visible in the desk. + * Tasks become inactive when task closes or when the desk becomes inactive. * @property visibleTasks task ids for active freeform tasks that are currently visible. There - * might be other active tasks in desktop mode that are not visible. + * might be other active tasks in a desk that are not visible. * @property minimizedTasks task ids for active freeform tasks that are currently minimized. * @property closingTasks task ids for tasks that are going to close, but are currently visible. * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom - * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode. + * @property fullImmersiveTaskId the task id of the desk's task that is in full-immersive mode. * @property topTransparentFullscreenTaskId the task id of any current top transparent - * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is - * closed or sent to back. (top is at index 0). + * fullscreen task launched on top of the desk. Cleared when the transparent task is closed or + * sent to back. (top is at index 0). * @property pipTaskId the task id of PiP task entered while in Desktop Mode. - * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop - * Mode session active. Only false when we are explicitly exiting Desktop Mode (via user - * action) while there is an active PiP window. + * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk + * active. Only false when we are explicitly exiting Desktop Mode (via user action) while + * there is an active PiP window. */ - private data class DesktopTaskData( + private data class Desk( + val deskId: Int, + val displayId: Int, val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), @@ -72,10 +82,13 @@ class DesktopRepository( var fullImmersiveTaskId: Int? = null, var topTransparentFullscreenTaskId: Int? = null, var pipTaskId: Int? = null, + // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId]. var pipShouldKeepDesktopActive: Boolean = true, ) { - fun deepCopy(): DesktopTaskData = - DesktopTaskData( + fun deepCopy(): Desk = + Desk( + deskId = deskId, + displayId = displayId, activeTasks = ArraySet(activeTasks), visibleTasks = ArraySet(visibleTasks), minimizedTasks = ArraySet(minimizedTasks), @@ -87,6 +100,8 @@ class DesktopRepository( pipShouldKeepDesktopActive = pipShouldKeepDesktopActive, ) + // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't + // reusable. fun clear() { activeTasks.clear() visibleTasks.clear() @@ -121,11 +136,11 @@ class DesktopRepository( private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null - private val desktopTaskDataByDisplayId = - object : SparseArray<DesktopTaskData>() { - /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */ - fun getOrCreate(displayId: Int): DesktopTaskData = - this[displayId] ?: DesktopTaskData().also { this[displayId] = it } + private val desktopData: DesktopData = + if (Flags.enableMultipleDesktopsBackend()) { + MultiDesktopData() + } else { + SingleDesktopData() } /** Adds [activeTasksListener] to be notified of updates to active tasks. */ @@ -136,10 +151,16 @@ class DesktopRepository( /** Adds [visibleTasksListener] to be notified of updates to visible tasks. */ fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) { visibleTasksListeners[visibleTasksListener] = executor - desktopTaskDataByDisplayId.keyIterator().forEach { - val visibleTaskCount = getVisibleTaskCount(it) - executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) } - } + desktopData + .desksSequence() + .groupBy { it.displayId } + .keys + .forEach { displayId -> + val visibleTaskCount = getVisibleTaskCount(displayId) + executor.execute { + visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTaskCount) + } + } } /** Updates tasks changes on all the active task listeners for given display id. */ @@ -147,9 +168,8 @@ class DesktopRepository( activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } } - /** Returns a list of all [DesktopTaskData] in the repository. */ - private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> = - desktopTaskDataByDisplayId.valueIterator().asSequence() + /** Returns a list of all [Desk]s in the repository. */ + private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence() /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */ fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) { @@ -179,99 +199,183 @@ class DesktopRepository( visibleTasksListeners.remove(visibleTasksListener) } - /** Adds task with [taskId] to the list of freeform tasks on [displayId]. */ + /** Adds the given desk under the given display. */ + fun addDesk(displayId: Int, deskId: Int) { + desktopData.getOrCreateDesk(displayId, deskId) + } + + /** Returns the default desk in the given display. */ + fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId + + /** Sets the given desk as the active one in the given display. */ + fun setActiveDesk(displayId: Int, deskId: Int) { + desktopData.setActiveDesk(displayId = displayId, deskId = deskId) + } + + /** + * Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) { addOrMoveFreeformTaskToTop(displayId, taskId) addActiveTask(displayId, taskId) updateTask(displayId, taskId, isVisible) } - /** Adds task with [taskId] to the list of active tasks on [displayId]. */ + /** + * Adds task with [taskId] to the list of active tasks on [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ private fun addActiveTask(displayId: Int, taskId: Int) { - // Removes task if it is active on another display excluding [displayId]. - removeActiveTask(taskId, excludedDisplayId = displayId) + val activeDeskId = + desktopData.getActiveDesk(displayId)?.deskId + ?: error("Expected active desk in display: $displayId") + + // Removes task if it is active on another desk excluding [activeDesk]. + removeActiveTask(taskId, excludedDeskId = activeDeskId) - if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) { - logD("Adds active task=%d displayId=%d", taskId, displayId) + if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) { + logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId) updateActiveTasksListeners(displayId) } } - /** Removes task from active task list of displays excluding the [excludedDisplayId]. */ - fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) { - desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData -> - if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) { - logD("Removed active task=%d displayId=%d", taskId, displayId) - updateActiveTasksListeners(displayId) + /** Removes task from active task list of desks excluding the [excludedDeskId]. */ + @VisibleForTesting + fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) { + val affectedDisplays = mutableSetOf<Int>() + desktopData.forAllDesks { displayId, desk -> + if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) { + logD( + "Removed active task=%d displayId=%d deskId=%d", + taskId, + displayId, + desk.deskId, + ) + affectedDisplays.add(displayId) } } + affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) } } - /** Adds given task to the closing task list for [displayId]. */ + /** + * Adds given task to the closing task list for [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun addClosingTask(displayId: Int, taskId: Int) { - if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) { - logD("Added closing task=%d displayId=%d", taskId, displayId) + val activeDeskId = + desktopData.getActiveDesk(displayId)?.deskId + ?: error("Expected active desk in display: $displayId") + if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) { + logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId) } else { // If the task hasn't been removed from closing list after it disappeared. - logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId) + logW( + "Task with taskId=%d displayId=%d deskId=%d is already closing", + taskId, + displayId, + activeDeskId, + ) } } - /** Removes task from the list of closing tasks for [displayId]. */ + /** Removes task from the list of closing tasks for all desks. */ fun removeClosingTask(taskId: Int) { - desktopTaskDataByDisplayId.forEach { displayId, taskInfo -> - if (taskInfo.closingTasks.remove(taskId)) { - logD("Removed closing task=%d displayId=%d", taskId, displayId) + desktopData.forAllDesks { desk -> + if (desk.closingTasks.remove(taskId)) { + logD("Removed closing task=%d deskId=%d", taskId, desk.deskId) } } } - fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks } + fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks } - fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks } + fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks } - fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks } + fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks } - fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks } + fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks } - /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */ + /** + * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of + * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY]. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean { - val seq = + val activeDesks = if (displayId != INVALID_DISPLAY) { - sequenceOf(desktopTaskDataByDisplayId[displayId]).filterNotNull() + setOfNotNull(desktopData.getActiveDesk(displayId)) } else { - desktopTaskDataSequence() + desktopData.getAllActiveDesks() } - return seq.any { - it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() == - taskId + return activeDesks.any { desk -> + desk.visibleTasks + .subtract(desk.closingTasks) + .subtract(desk.minimizedTasks) + .singleOrNull() == taskId } } + /** + * Returns the active tasks in the given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ + @VisibleForTesting fun getActiveTasks(displayId: Int): ArraySet<Int> = - ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks) + ArraySet(desktopData.getActiveDesk(displayId)?.activeTasks) + /** + * Returns the minimized tasks in the given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getMinimizedTasks(displayId: Int): ArraySet<Int> = - ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks) + ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks) - /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */ + /** + * Returns all active non-minimized tasks for [displayId] ordered from top to bottom. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getExpandedTasksOrdered(displayId: Int): List<Int> = getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } - /** Returns the count of active non-minimized tasks for [displayId]. */ + /** + * Returns the count of active non-minimized tasks for [displayId]. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getExpandedTaskCount(displayId: Int): Int { return getActiveTasks(displayId).count { !isMinimizedTask(it) } } - /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */ + /** + * Returns a list of freeform tasks, ordered from top-bottom (top at index 0). + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ + @VisibleForTesting fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> = - ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList()) + ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList()) + + /** Returns the tasks inside the given desk. */ + fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> = + desktopData.getDesk(deskId)?.activeTasks?.toSet() + ?: run { + logW("getTasksInDesk: could not find desk: deskId=%d", deskId) + emptySet() + } /** Removes task from visible tasks of all displays except [excludedDisplayId]. */ private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) { - desktopTaskDataByDisplayId.forEach { displayId, data -> - if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) { - notifyVisibleTaskListeners(displayId, data.visibleTasks.size) + desktopData.forAllDesks { displayId, desk -> + if (displayId != excludedDisplayId && desk.visibleTasks.remove(taskId)) { + notifyVisibleTaskListeners(displayId, desk.visibleTasks.size) } } } @@ -281,6 +385,8 @@ class DesktopRepository( * * If task was visible on a different display with a different [displayId], removes from the set * of visible tasks on that display and notifies listeners. + * + * TODO: b/389960283 - add explicit [deskId] argument. */ fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) { logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible) @@ -295,10 +401,11 @@ class DesktopRepository( } val prevCount = getVisibleTaskCount(displayId) if (isVisible) { - desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId) + desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId) + ?: error("Expected non-null active desk in display $displayId") unminimizeTask(displayId, taskId) } else { - desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId) + desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId) } val newCount = getVisibleTaskCount(displayId) if (prevCount != newCount) { @@ -316,57 +423,94 @@ class DesktopRepository( } } - /** Set whether the given task is the Desktop-entered PiP task in this display. */ + /** + * Set whether the given task is the Desktop-entered PiP task in this display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) { - val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) + val activeDesk = + desktopData.getActiveDesk(displayId) + ?: error("Expected active desk in display: $displayId") if (enterPip) { - desktopData.pipTaskId = taskId - desktopData.pipShouldKeepDesktopActive = true + activeDesk.pipTaskId = taskId + activeDesk.pipShouldKeepDesktopActive = true } else { - desktopData.pipTaskId = - if (desktopData.pipTaskId == taskId) null + activeDesk.pipTaskId = + if (activeDesk.pipTaskId == taskId) null else { logW( - "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}" + "setTaskInPip: taskId=%d did not match saved taskId=%d", + taskId, + activeDesk.pipTaskId, ) - desktopData.pipTaskId + activeDesk.pipTaskId } } notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId)) } - /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */ + /** + * Returns whether there is a PiP that was entered/minimized from Desktop in this display's + * active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean = - desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null + desktopData.getActiveDesk(displayId)?.pipTaskId != null - /** Returns whether the given task is the Desktop-entered PiP task in this display. */ + /** + * Returns whether the given task is the Desktop-entered PiP task in this display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean = - desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId + desktopData.getActiveDesk(displayId)?.pipTaskId == taskId - /** Returns whether Desktop session should be active in this display due to active PiP. */ + /** + * Returns whether a desk should be active in this display due to active PiP. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun shouldDesktopBeActiveForPip(displayId: Int): Boolean = Flags.enableDesktopWindowingPip() && isMinimizedPipPresentInDisplay(displayId) && - desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive + (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false) - /** Saves whether a PiP window should keep Desktop session active in this display. */ + /** + * Saves whether a PiP window should keep Desktop session active in this display. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) { - desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive + desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive } - /** Saves callback to handle a pending PiP transition being aborted. */ - fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) { + /** + * Saves callback to handle a pending PiP transition being aborted. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ + fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) { onPipAbortedCallback = callbackIfPipAborted } - /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */ + /** + * Invokes callback to handle a pending PiP transition with the given task id being aborted. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun onPipAborted(displayId: Int, pipTaskId: Int) { onPipAbortedCallback?.invoke(displayId, pipTaskId) } - /** Set whether the given task is the full-immersive task in this display. */ + /** + * Set whether the given task is the full-immersive task in this display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) { - val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) + val desktopData = desktopData.getActiveDesk(displayId) ?: return if (immersive) { desktopData.fullImmersiveTaskId = taskId } else { @@ -378,25 +522,41 @@ class DesktopRepository( /* Whether the task is in full-immersive state. */ fun isTaskInFullImmersiveState(taskId: Int): Boolean { - return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId } + return desksSequence().any { taskId == it.fullImmersiveTaskId } } - /** Returns the task that is currently in immersive mode in this display, or null. */ + /** + * Returns the task that is currently in immersive mode in this display, or null. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getTaskInFullImmersiveState(displayId: Int): Int? = - desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId + desktopData.getActiveDesk(displayId)?.fullImmersiveTaskId - /** Sets the top transparent fullscreen task id for a given display. */ + /** + * Sets the top transparent fullscreen task id for a given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) { - desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId } - /** Returns the top transparent fullscreen task id for a given display, or null. */ + /** + * Returns the top transparent fullscreen task id for a given display's active desk, or null. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getTopTransparentFullscreenTaskId(displayId: Int): Int? = - desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId - /** Clears the top transparent fullscreen task id info for a given display. */ + /** + * Clears the top transparent fullscreen task id info for a given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun clearTopTransparentFullscreenTaskId(displayId: Int) { - desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null } private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) { @@ -409,22 +569,35 @@ class DesktopRepository( } } - /** Gets number of visible freeform tasks on given [displayId] */ + /** + * Gets number of visible freeform tasks on given [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getVisibleTaskCount(displayId: Int): Int = - desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size - ?: 0.also { logD("getVisibleTaskCount=$it") } + (desktopData.getActiveDesk(displayId)?.visibleTasks?.size ?: 0).also { + logD("getVisibleTaskCount=$it") + } /** * Adds task (or moves if it already exists) to the top of the ordered list. * * Unminimizes the task if it is minimized. + * + * TODO: b/389960283 - add explicit [deskId] argument. */ private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) { - logD("Add or move task to top: display=%d taskId=%d", taskId, displayId) - desktopTaskDataByDisplayId.forEach { _, value -> - value.freeformTasksInZOrder.remove(taskId) - } - desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId) + val activeDesk = + desktopData.getActiveDesk(displayId) + ?: error("Expected a desk to be active in display: $displayId") + logD( + "Add or move task to top: display=%d taskId=%d deskId=%d", + taskId, + displayId, + activeDesk.deskId, + ) + desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) } + activeDesk.freeformTasksInZOrder.add(0, taskId) // Unminimize the task if it is minimized. unminimizeTask(displayId, taskId) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -432,7 +605,11 @@ class DesktopRepository( } } - /** Minimizes the task for [taskId] and [displayId] */ + /** + * Minimizes the task for [taskId] and [displayId]'s active display. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun minimizeTask(displayId: Int, taskId: Int) { if (displayId == INVALID_DISPLAY) { // When a task vanishes it doesn't have a displayId. Find the display of the task and @@ -441,7 +618,8 @@ class DesktopRepository( ?: logW("Minimize task: No display id found for task: taskId=%d", taskId) } else { logD("Minimize Task: display=%d, task=%d", displayId, taskId) - desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId) + desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId) + ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId) } updateTask(displayId, taskId, isVisible = false) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -449,26 +627,42 @@ class DesktopRepository( } } - /** Unminimizes the task for [taskId] and [displayId] */ + /** + * Unminimizes the task for [taskId] and [displayId]. + * + * TODO: b/389960283 - consider adding an explicit [deskId] argument. + */ fun unminimizeTask(displayId: Int, taskId: Int) { logD("Unminimize Task: display=%d, task=%d", displayId, taskId) - desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) - ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) + var removed = false + desktopData.forAllDesks(displayId) { desk -> + if (desk.minimizedTasks.remove(taskId)) { + removed = true + } + } + if (!removed) { + logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) + } } private fun getDisplayIdForTask(taskId: Int): Int? { - desktopTaskDataByDisplayId.forEach { displayId, data -> - if (taskId in data.freeformTasksInZOrder) { - return displayId + var displayForTask: Int? = null + desktopData.forAllDesks { displayId, desk -> + if (taskId in desk.freeformTasksInZOrder) { + displayForTask = displayId } } - logW("No display id found for task: taskId=%d", taskId) - return null + if (displayForTask == null) { + logW("No display id found for task: taskId=%d", taskId) + } + return displayForTask } /** * Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id * will be looked up from the task id. + * + * TODO: b/389960283 - consider adding an explicit [deskId] argument. */ fun removeTask(displayId: Int, taskId: Int) { logD("Removes freeform task: taskId=%d", taskId) @@ -483,13 +677,17 @@ class DesktopRepository( /** Removes given task from a valid [displayId] and updates the repository state. */ private fun removeTaskFromDisplay(displayId: Int, taskId: Int) { logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId) - desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId) + desktopData.forAllDesks(displayId) { desk -> + if (desk.freeformTasksInZOrder.remove(taskId)) { + logD( + "Remaining freeform tasks in desk: %d, tasks: %s", + desk.deskId, + desk.freeformTasksInZOrder.toDumpString(), + ) + } + } boundsBeforeMaximizeByTaskId.remove(taskId) boundsBeforeFullImmersiveByTaskId.remove(taskId) - logD( - "Remaining freeform tasks: %s", - desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(), - ) // Remove task from unminimized task if it is minimized. unminimizeTask(displayId, taskId) // Mark task as not in immersive if it was immersive. @@ -502,15 +700,18 @@ class DesktopRepository( } /** - * Removes the desktop for the given [displayId] and returns the active tasks on that desktop. + * Removes the active desk for the given [displayId] and returns the active tasks on that desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. */ - fun removeDesktop(displayId: Int): ArraySet<Int> { - if (!desktopTaskDataByDisplayId.contains(displayId)) { - logW("Could not find desktop to remove: displayId=%d", displayId) + fun removeDesk(displayId: Int): ArraySet<Int> { + val desk = desktopData.getActiveDesk(displayId) + if (desk == null) { + logW("Could not find desk to remove: displayId=%d", displayId) return ArraySet() } - val activeTasks = ArraySet(desktopTaskDataByDisplayId[displayId].activeTasks) - desktopTaskDataByDisplayId[displayId].clear() + val activeTasks = ArraySet(desk.activeTasks) + desktopData.remove(desk.deskId) return activeTasks } @@ -564,19 +765,20 @@ class DesktopRepository( fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) = boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds)) + /** TODO: b/389960283 - consider updating only the changing desks. */ private fun updatePersistentRepository(displayId: Int) { - // Create a deep copy of the data - desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy -> - mainCoroutineScope.launch { + val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList() + mainCoroutineScope.launch { + desks.forEach { desk -> try { persistentRepository.addOrUpdateDesktop( - // Use display id as desktop id for now since only once desktop per display + // Use display id as desk id for now since only once desk per display // is supported. userId = userId, - desktopId = displayId, - visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks, - minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks, - freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder, + desktopId = desk.deskId, + visibleTasks = desk.visibleTasks, + minimizedTasks = desk.minimizedTasks, + freeformTasksInZOrder = desk.freeformTasksInZOrder, ) } catch (exception: Exception) { logE( @@ -598,20 +800,27 @@ class DesktopRepository( private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " - desktopTaskDataByDisplayId.forEach { displayId, data -> - pw.println("${prefix}Display $displayId:") - pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") - pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") - pw.println( - "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}" - ) - pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}") - pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}") - pw.println( - "${innerPrefix}topTransparentFullscreenTaskId=" + - "${data.topTransparentFullscreenTaskId}" - ) - } + desktopData + .desksSequence() + .groupBy { it.displayId } + .forEach { (displayId, desks) -> + pw.println("${prefix}Display #$displayId:") + desks.forEach { desk -> + pw.println("${innerPrefix}Desk #${desk.deskId}:") + pw.print("$innerPrefix activeTasks=") + pw.println(desk.activeTasks.toDumpString()) + pw.print("$innerPrefix visibleTasks=") + pw.println(desk.visibleTasks.toDumpString()) + pw.print("$innerPrefix freeformTasksInZOrder=") + pw.println(desk.freeformTasksInZOrder.toDumpString()) + pw.print("$innerPrefix minimizedTasks=") + pw.println(desk.minimizedTasks.toDumpString()) + pw.print("$innerPrefix fullImmersiveTaskId=") + pw.println(desk.fullImmersiveTaskId) + pw.print("$innerPrefix topTransparentFullscreenTaskId=") + pw.println(desk.topTransparentFullscreenTaskId) + } + } } /** Listens to changes for active tasks in desktop mode. */ @@ -624,6 +833,227 @@ class DesktopRepository( fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {} } + /** An interface for the desktop hierarchy's data managed by this repository. */ + private interface DesktopData { + /** + * Returns the existing desk or creates a new entry if needed. + * + * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in + * all devices / form-factors. + */ + fun getOrCreateDesk(displayId: Int, deskId: Int): Desk + + /** Returns the desk with the given id, or null if it does not exist. */ + fun getDesk(deskId: Int): Desk? + + /** Returns the active desk in this diplay, or null if none are active. */ + fun getActiveDesk(displayId: Int): Desk? + + /** Sets the given desk as the active desk in the given display. */ + fun setActiveDesk(displayId: Int, deskId: Int) + + /** + * Returns the default desk in the given display. Useful when the system wants to activate a + * desk but doesn't care about which one it activates (e.g. when putting a window into a + * desk using the App Handle). May return null if the display does not support desks. + * + * TODO: 389787966 - consider removing or renaming. In practice, this is needed for + * soon-to-be deprecated IDesktopMode APIs, adb commands or entry-points into the only + * desk (single-desk devices) or the most-recent desk (multi-desk devices). + */ + fun getDefaultDesk(displayId: Int): Desk? + + /** Returns all the active desks of all displays. */ + fun getAllActiveDesks(): Set<Desk> + + /** Returns the number of desks in the given display. */ + fun getNumberOfDesks(displayId: Int): Int + + /** Applies a function to all desks. */ + fun forAllDesks(consumer: (Desk) -> Unit) + + /** Applies a function to all desks. */ + fun forAllDesks(consumer: (displayId: Int, Desk) -> Unit) + + /** Applies a function to all desks under the given display. */ + fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) + + /** Returns a sequence of all desks. */ + fun desksSequence(): Sequence<Desk> + + /** Returns a sequence of all desks under the given display. */ + fun desksSequence(displayId: Int): Sequence<Desk> + + /** Remove an existing desk if it exists. */ + fun remove(deskId: Int) + + /** Returns the id of the display where the given desk is located. */ + fun getDisplayForDesk(deskId: Int): Int + } + + /** + * A [DesktopData] implementation that only supports one desk per display. + * + * Internally, it reuses the displayId as that display's single desk's id. + */ + private class SingleDesktopData : DesktopData { + private val deskByDisplayId = + object : SparseArray<Desk>() { + /** Gets [Desk] for existing [displayId] or creates a new one. */ + fun getOrCreate(displayId: Int): Desk = + this[displayId] + ?: Desk(deskId = displayId, displayId = displayId).also { + this[displayId] = it + } + } + + override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk { + check(displayId == deskId) + return deskByDisplayId.getOrCreate(displayId) + } + + override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId) + + override fun getActiveDesk(displayId: Int): Desk { + // TODO: 389787966 - consider migrating to an "active" state instead of checking the + // number of visible active tasks, PIP in desktop, and empty desktop logic. In + // practice, existing single-desktop devices are ok with this function returning the + // only desktop, even if it's not active. + return deskByDisplayId.getOrCreate(displayId) + } + + override fun setActiveDesk(displayId: Int, deskId: Int) { + // No-op, in single-desk setups, which desktop is "active" is determined by the + // existence of visible desktop windows, among other factors. + } + + override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId) + + override fun getAllActiveDesks(): Set<Desk> = + deskByDisplayId.valueIterator().asSequence().toSet() + + override fun getNumberOfDesks(displayId: Int): Int = 1 + + override fun forAllDesks(consumer: (Desk) -> Unit) { + deskByDisplayId.forEach { _, desk -> consumer(desk) } + } + + override fun forAllDesks(consumer: (Int, Desk) -> Unit) { + deskByDisplayId.forEach { displayId, desk -> consumer(displayId, desk) } + } + + override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) { + consumer(getOrCreateDesk(displayId, displayId)) + } + + override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence() + + override fun desksSequence(displayId: Int): Sequence<Desk> = + deskByDisplayId[displayId]?.let { sequenceOf(it) } ?: emptySequence() + + override fun remove(deskId: Int) { + deskByDisplayId[deskId]?.clear() + } + + override fun getDisplayForDesk(deskId: Int): Int = deskId + } + + /** A [DesktopData] implementation that supports multiple desks. */ + private class MultiDesktopData : DesktopData { + private val desktopDisplays = SparseArray<DesktopDisplay>() + + override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk { + val display = + desktopDisplays[displayId] + ?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it } + val desk = + display.orderedDesks.find { desk -> desk.deskId == deskId } + ?: Desk(deskId = deskId, displayId = displayId).also { + display.orderedDesks.add(it) + } + return desk + } + + override fun getDesk(deskId: Int): Desk? { + desktopDisplays.forEach { _, display -> + val desk = display.orderedDesks.find { desk -> desk.deskId == deskId } + if (desk != null) { + return desk + } + } + return null + } + + override fun getActiveDesk(displayId: Int): Desk? { + val display = desktopDisplays[displayId] ?: return null + if (display.activeDeskId == null) return null + return display.orderedDesks.find { it.deskId == display.activeDeskId } + } + + override fun setActiveDesk(displayId: Int, deskId: Int) { + val display = + desktopDisplays[displayId] ?: error("Expected display#$displayId to exist") + val desk = display.orderedDesks.single { it.deskId == deskId } + display.activeDeskId = desk.deskId + } + + override fun getDefaultDesk(displayId: Int): Desk? { + val display = desktopDisplays[displayId] ?: return null + return display.orderedDesks.firstOrNull() + } + + override fun getAllActiveDesks(): Set<Desk> { + return desktopDisplays + .valueIterator() + .asSequence() + .filter { display -> display.activeDeskId != null } + .map { display -> + display.orderedDesks.single { it.deskId == display.activeDeskId } + } + .toSet() + } + + override fun getNumberOfDesks(displayId: Int): Int = + desktopDisplays[displayId]?.orderedDesks?.size ?: 0 + + override fun forAllDesks(consumer: (Desk) -> Unit) { + desktopDisplays.forEach { _, display -> display.orderedDesks.forEach { consumer(it) } } + } + + override fun forAllDesks(consumer: (Int, Desk) -> Unit) { + desktopDisplays.forEach { _, display -> + display.orderedDesks.forEach { consumer(display.displayId, it) } + } + } + + override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) { + desktopDisplays + .valueIterator() + .asSequence() + .filter { display -> display.displayId == displayId } + .flatMap { display -> display.orderedDesks.asSequence() } + .forEach { desk -> consumer(desk) } + } + + override fun desksSequence(): Sequence<Desk> = + desktopDisplays.valueIterator().asSequence().flatMap { display -> + display.orderedDesks.asSequence() + } + + override fun desksSequence(displayId: Int): Sequence<Desk> = + desktopDisplays[displayId]?.orderedDesks?.asSequence() ?: emptySequence() + + override fun remove(deskId: Int) { + desktopDisplays.forEach { _, display -> + display.orderedDesks.removeIf { it.deskId == deskId } + } + } + + override fun getDisplayForDesk(deskId: Int): Int = + getAllActiveDesks().find { it.deskId == deskId }?.displayId + ?: error("Display for desk=$deskId not found") + } + private fun logD(msg: String, vararg arguments: Any?) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } 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 f38957e48dbf..172410d0482c 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 @@ -35,6 +35,7 @@ import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.os.Binder +import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.SystemProperties @@ -86,6 +87,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.compatui.isTransparentTask import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState @@ -465,7 +467,9 @@ class DesktopTasksController( desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( FREEFORM_ANIMATION_DURATION ) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } + taskIdToMinimize?.let { + addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT) + } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) return true } @@ -511,7 +515,9 @@ class DesktopTasksController( desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( FREEFORM_ANIMATION_DURATION ) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } + taskIdToMinimize?.let { + addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT) + } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } @@ -572,7 +578,9 @@ class DesktopTasksController( DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt() ) transition?.let { - taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) } + taskIdToMinimize?.let { taskId -> + addPendingMinimizeTransition(it, taskId, MinimizeReason.TASK_LIMIT) + } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } } @@ -621,7 +629,7 @@ class DesktopTasksController( ?.runOnTransitionStart } - fun minimizeTask(taskInfo: RunningTaskInfo) { + fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val wct = WindowContainerTransaction() val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false @@ -641,16 +649,16 @@ class DesktopTasksController( freeformTaskTransitionStarter.startPipTransition(wct) taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true) taskRepository.setOnPipAbortedCallback { displayId, taskId -> - minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!) + minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason) taskRepository.setTaskInPip(displayId, taskId, enterPip = false) } return } - minimizeTaskInner(taskInfo) + minimizeTaskInner(taskInfo, minimizeReason) } - private fun minimizeTaskInner(taskInfo: RunningTaskInfo) { + private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() @@ -670,6 +678,7 @@ class DesktopTasksController( transition = transition, displayId = displayId, taskId = taskId, + minimizeReason = minimizeReason, ) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) @@ -825,7 +834,7 @@ class DesktopTasksController( minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, ) - taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) } + taskIdToMinimize?.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t } @@ -845,7 +854,7 @@ class DesktopTasksController( ) val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler) remoteTransitionHandler.setTransition(t) - taskIdToMinimize.let { addPendingMinimizeTransition(t, it) } + taskIdToMinimize.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t } @@ -884,6 +893,36 @@ class DesktopTasksController( } /** + * Start an intent through a launch transition for starting tasks whose transition does not get + * handled by [handleRequest] + */ + fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) { + val wct = WindowContainerTransaction() + val displayLayout = displayController.getDisplayLayout(displayId) ?: return + val bounds = calculateDefaultDesktopTaskBounds(displayLayout) + if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) { + cascadeWindow(bounds, displayLayout, displayId) + } + val pendingIntent = + PendingIntent.getActivity( + context, + /* requestCode= */ 0, + intent, + PendingIntent.FLAG_IMMUTABLE, + ) + val ops = + ActivityOptions.fromBundle(options).apply { + launchWindowingMode = WINDOWING_MODE_FREEFORM + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + launchBounds = bounds + } + + wct.sendPendingIntent(pendingIntent, intent, ops.toBundle()) + startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + } + + /** * Move [task] to display with [displayId]. * * No-op if task is already on that display per [RunningTaskInfo.displayId]. @@ -1867,7 +1906,7 @@ class DesktopTasksController( val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) if (taskIdToMinimize != null) { - addPendingMinimizeTransition(transition, taskIdToMinimize) + addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT) return wct } if (!wct.isEmpty) { @@ -1901,7 +1940,9 @@ class DesktopTasksController( // Desktop Mode is already showing and we're launching a new Task - we might need to // minimize another Task. val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } + taskIdToMinimize?.let { + addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT) + } addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) desktopImmersiveController.exitImmersiveIfApplicable( transition, @@ -2149,13 +2190,18 @@ class DesktopTasksController( .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent) } - private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) { + private fun addPendingMinimizeTransition( + transition: IBinder, + taskIdToMinimize: Int, + minimizeReason: MinimizeReason, + ) { val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) desktopTasksLimiter.ifPresent { it.addPendingMinimizeChange( transition = transition, displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY, taskId = taskIdToMinimize, + minimizeReason = minimizeReason, ) } } @@ -2185,7 +2231,7 @@ class DesktopTasksController( fun removeDesktop(displayId: Int) { if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return - val tasksToRemove = taskRepository.removeDesktop(displayId) + val tasksToRemove = taskRepository.removeDesk(displayId) val wct = WindowContainerTransaction() tasksToRemove.forEach { val task = shellTaskOrganizer.getRunningTaskInfo(it) @@ -2425,6 +2471,25 @@ class DesktopTasksController( // Update task bounds so that the task position will match the position of its leash val wct = WindowContainerTransaction() wct.setBounds(taskInfo.token, destinationBounds) + + // TODO: b/362720497 - reparent to a specific desk within the target display. + // Reparent task if it has been moved to a new display. + if (Flags.enableConnectedDisplaysWindowDrag()) { + val newDisplayId = motionEvent.getDisplayId() + if (newDisplayId != taskInfo.getDisplayId()) { + val displayAreaInfo = + rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId) + if (displayAreaInfo == null) { + logW( + "Task reparent cannot find DisplayAreaInfo for displayId=%d", + newDisplayId, + ) + } else { + wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true) + } + } + } + transitions.startTransition(TRANSIT_CHANGE, wct, null) releaseVisualIndicator() @@ -2885,6 +2950,12 @@ class DesktopTasksController( c.moveToNextDisplay(taskId) } } + + override fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) { + executeRemoteCallWithTaskPermission(controller, "startLaunchIntentTransition") { c -> + c.startLaunchIntentTransition(intent, options, displayId) + } + } } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index e4a28e9efe60..204b39645248 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -30,6 +30,7 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.UserChangeListener @@ -67,12 +68,21 @@ class DesktopTasksLimiter( logV("Starting limiter with a maximum of %d tasks", maxTasksLimit) } - private data class TaskDetails( + data class TaskDetails( val displayId: Int, val taskId: Int, - var transitionInfo: TransitionInfo?, + var transitionInfo: TransitionInfo? = null, + val minimizeReason: MinimizeReason? = null, ) + /** + * Returns the task being minimized in the given transition if that transition is a pending or + * active minimize transition. + */ + fun getMinimizingTask(transition: IBinder): TaskDetails? { + return minimizeTransitionObserver.getMinimizingTask(transition) + } + // TODO(b/333018485): replace this observer when implementing the minimize-animation private inner class MinimizeTransitionObserver : TransitionObserver { private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() @@ -82,6 +92,11 @@ class DesktopTasksLimiter( pendingTransitionTokensAndTasks[transition] = taskDetails } + fun getMinimizingTask(transition: IBinder): TaskDetails? { + return pendingTransitionTokensAndTasks[transition] + ?: activeTransitionTokensAndTasks[transition] + } + override fun onTransitionReady( transition: IBinder, info: TransitionInfo, @@ -89,6 +104,14 @@ class DesktopTasksLimiter( finishTransaction: SurfaceControl.Transaction, ) { val taskRepository = desktopUserRepositories.current + handleMinimizeTransition(taskRepository, transition, info) + } + + private fun handleMinimizeTransition( + taskRepository: DesktopRepository, + transition: IBinder, + info: TransitionInfo, + ) { val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return if (!isTaskReadyForMinimize(info, taskToMinimize)) { @@ -241,10 +264,15 @@ class DesktopTasksLimiter( * Add a pending minimize transition change to update the list of minimized apps once the * transition goes through. */ - fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) { + fun addPendingMinimizeChange( + transition: IBinder, + displayId: Int, + taskId: Int, + minimizeReason: MinimizeReason, + ) { minimizeTransitionObserver.addPendingTransitionToken( transition, - TaskDetails(displayId, taskId, transitionInfo = null), + TaskDetails(displayId, taskId, transitionInfo = null, minimizeReason = minimizeReason), ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index fa383cb55118..54f031293486 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -17,6 +17,8 @@ package com.android.wm.shell.desktopmode; import android.app.ActivityManager.RunningTaskInfo; +import android.content.Intent; +import android.os.Bundle; import android.window.RemoteTransition; import com.android.wm.shell.desktopmode.IDesktopTaskListener; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; @@ -61,4 +63,7 @@ interface IDesktopMode { /** Move a task with given `taskId` to external display */ void moveToExternalDisplay(int taskId); + + /** Start a transition when launching an intent in desktop mode */ + void startLaunchIntentTransition(in Intent intent, in Bundle options, in int displayId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 9c3e815b389d..912d3839fae7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -1317,14 +1317,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, int launcherRotation, - Rect keepClearArea) { + public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo, + int launcherRotation, Rect keepClearArea) { Rect[] result = new Rect[1]; executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome", (controller) -> { - result[0] = controller.startSwipePipToHome(componentName, activityInfo, - pictureInPictureParams, launcherRotation, keepClearArea); + result[0] = controller.startSwipePipToHome(taskInfo.topActivity, + taskInfo.topActivityInfo, taskInfo.pictureInPictureParams, + launcherRotation, keepClearArea); }, true /* blocking */); return result[0]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java index 63c151268bdb..a033b824aa28 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java @@ -50,6 +50,11 @@ public class PipAlphaAnimator extends ValueAnimator { private final SurfaceControl mLeash; private final SurfaceControl.Transaction mStartTransaction; + private final SurfaceControl.Transaction mFinishTransaction; + + private final int mDirection; + private final int mCornerRadius; + private final int mShadowRadius; private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { @Override @@ -59,6 +64,7 @@ public class PipAlphaAnimator extends ValueAnimator { mAnimationStartCallback.run(); } if (mStartTransaction != null) { + onAlphaAnimationUpdate(getStartAlphaValue(), mStartTransaction); mStartTransaction.apply(); } } @@ -66,6 +72,10 @@ public class PipAlphaAnimator extends ValueAnimator { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); + if (mFinishTransaction != null) { + onAlphaAnimationUpdate(getEndAlphaValue(), mFinishTransaction); + mFinishTransaction.apply(); + } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); } @@ -77,8 +87,9 @@ public class PipAlphaAnimator extends ValueAnimator { @Override public void onAnimationUpdate(@NonNull ValueAnimator animation) { final float alpha = (Float) animation.getAnimatedValue(); - mSurfaceControlTransactionFactory.getTransaction() - .setAlpha(mLeash, alpha).apply(); + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + onAlphaAnimationUpdate(alpha, tx); } }; @@ -91,19 +102,21 @@ public class PipAlphaAnimator extends ValueAnimator { public PipAlphaAnimator(Context context, SurfaceControl leash, - SurfaceControl.Transaction tx, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, @Fade int direction) { mLeash = leash; - mStartTransaction = tx; - if (direction == FADE_IN) { - setFloatValues(0f, 1f); - } else { // direction == FADE_OUT - setFloatValues(1f, 0f); - } + mStartTransaction = startTransaction; + mFinishTransaction = finishTransaction; + + mDirection = direction; + setFloatValues(getStartAlphaValue(), getEndAlphaValue()); mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); final int enterAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); + mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); + mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius); setDuration(enterAnimationDuration); addListener(mAnimatorListener); addUpdateListener(mAnimatorUpdateListener); @@ -117,6 +130,21 @@ public class PipAlphaAnimator extends ValueAnimator { mAnimationEndCallback = runnable; } + private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) { + tx.setAlpha(mLeash, alpha) + .setCornerRadius(mLeash, mCornerRadius) + .setShadowRadius(mLeash, mShadowRadius); + tx.apply(); + } + + private float getStartAlphaValue() { + return mDirection == FADE_IN ? 0f : 1f; + } + + private float getEndAlphaValue() { + return mDirection == FADE_IN ? 1f : 0f; + } + @VisibleForTesting void setSurfaceControlTransactionFactory( @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 562b26014bf3..b1984ccef4cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -40,6 +40,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayChangeController; @@ -358,10 +359,21 @@ public class PipController implements ConfigurationChangeListener, // private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, + int displayId, PictureInPictureParams pictureInPictureParams, int launcherRotation, Rect hotseatKeepClearArea) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "getSwipePipToHomeBounds: %s", componentName); + + // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct + // display info that PiP is entering in. + if (Flags.enableConnectedDisplaysPip()) { + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); + if (displayLayout != null) { + mPipDisplayLayoutState.setDisplayId(displayId); + mPipDisplayLayoutState.setDisplayLayout(displayLayout); + } + } + // Preemptively add the keep clear area for Hotseat, so that it is taken into account // when calculating the entry destination bounds of PiP window. mPipBoundsState.setNamedUnrestrictedKeepClearArea( @@ -592,14 +604,14 @@ public class PipController implements ConfigurationChangeListener, } @Override - public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, int launcherRotation, - Rect keepClearArea) { + public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo, + int launcherRotation, Rect keepClearArea) { Rect[] result = new Rect[1]; executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome", (controller) -> { - result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo, - pictureInPictureParams, launcherRotation, keepClearArea); + result[0] = controller.getSwipePipToHomeBounds(taskInfo.topActivity, + taskInfo.topActivityInfo, taskInfo.displayId, + taskInfo.pictureInPictureParams, launcherRotation, keepClearArea); }, true /* blocking */); return result[0]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index ed532cad0523..21b0820f523a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -294,7 +294,8 @@ public class PipScheduler { interface PipAlphaAnimatorSupplier { PipAlphaAnimator get(@NonNull Context context, SurfaceControl leash, - SurfaceControl.Transaction tx, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, @PipAlphaAnimator.Fade int direction); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 4902455cae16..8cba076d28f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -59,6 +59,8 @@ import com.android.internal.util.Preconditions; import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ComponentUtils; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; @@ -112,6 +114,7 @@ public class PipTransition extends PipTransitionController implements private final PipScheduler mPipScheduler; private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; + private final DisplayController mDisplayController; private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; private final Optional<DesktopWallpaperActivityTokenProvider> mDesktopWallpaperActivityTokenProviderOptional; @@ -151,6 +154,7 @@ public class PipTransition extends PipTransitionController implements PipTransitionState pipTransitionState, PipDisplayLayoutState pipDisplayLayoutState, PipUiStateChangeController pipUiStateChangeController, + DisplayController displayController, Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, Optional<DesktopWallpaperActivityTokenProvider> desktopWallpaperActivityTokenProviderOptional) { @@ -164,6 +168,7 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState = pipTransitionState; mPipTransitionState.addPipTransitionStateChangedListener(this); mPipDisplayLayoutState = pipDisplayLayoutState; + mDisplayController = displayController; mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; mDesktopWallpaperActivityTokenProviderOptional = desktopWallpaperActivityTokenProviderOptional; @@ -513,7 +518,7 @@ public class PipTransition extends PipTransitionController implements private void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash, @NonNull Runnable onAnimationEnd) { PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash, - null /* startTx */, PipAlphaAnimator.FADE_OUT); + null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT); animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS); animator.setAnimationEndCallback(onAnimationEnd); animator.start(); @@ -604,7 +609,7 @@ public class PipTransition extends PipTransitionController implements .setAlpha(pipLeash, 0f); PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction, - PipAlphaAnimator.FADE_IN); + finishTransaction, PipAlphaAnimator.FADE_IN); // This should update the pip transition state accordingly after we stop playing. animator.setAnimationEndCallback(this::finishTransition); cacheAndStartTransitionAnimator(animator); @@ -699,7 +704,7 @@ public class PipTransition extends PipTransitionController implements finishTransaction.setAlpha(pipChange.getLeash(), 0f); if (mPendingRemoveWithFadeout) { PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(), - startTransaction, PipAlphaAnimator.FADE_OUT); + startTransaction, finishTransaction, PipAlphaAnimator.FADE_OUT); animator.setAnimationEndCallback(this::finishTransition); animator.start(); } else { @@ -824,6 +829,17 @@ public class PipTransition extends PipTransitionController implements mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo, pipParams, mPipBoundsAlgorithm); + // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct + // display info that PiP is entering in. + if (Flags.enableConnectedDisplaysPip()) { + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout( + pipTask.displayId); + if (displayLayout != null) { + mPipDisplayLayoutState.setDisplayId(pipTask.displayId); + mPipDisplayLayoutState.setDisplayLayout(displayLayout); + } + } + // calculate the entry bounds and notify core to move task to pinned with final bounds final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); mPipBoundsState.setBounds(entryBounds); 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 975b65023f20..0182588398a4 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 @@ -54,7 +54,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.ProtoLog; -import com.android.launcher3.Flags; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; @@ -553,9 +552,7 @@ public class RecentTasksController implements TaskStackListenerCallback, groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - if ( - Flags.enableUseTopVisibleActivityForExcludeFromRecentTask() - && isWallpaperTask(taskInfo)) { + if (isWallpaperTask(taskInfo)) { // Don't add the wallpaper task as an entry in grouped tasks continue; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index aeccd86e122c..36eaebdf4fff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -1148,9 +1148,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, change, layer, info, t, mLeashMap); appearedTargets[nextTargetIdx++] = target; // reparent into the original `mInfo` since that's where we are animating. - final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); + final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo); final boolean wasClosing = closingIdx >= 0; - t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash()); + t.reparent(target.leash, root.getLeash()); + t.setPosition(target.leash, + change.getStartAbsBounds().left - root.getOffset().x, + change.getStartAbsBounds().top - root.getOffset().y); t.setLayer(target.leash, layer); if (wasClosing) { // App was previously visible and is closing diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index c9136b4ad18d..37c93518998a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -138,6 +138,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.OffscreenTouchZone; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; @@ -556,6 +557,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + if (PipUtils.isPip2ExperimentEnabled() + && request.getPipChange() != null && getSplitPosition( + request.getPipChange().getTaskInfo().taskId) != SPLIT_POSITION_UNDEFINED) { + // In PiP2, PiP-able task can also come in through the pip change request field. + return true; + } + // If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA // and file a TRANSIT_PIP transition when finishing transitions. // @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index e61929fef312..2133275cde61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -17,13 +17,14 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; -import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; +import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -36,6 +37,7 @@ import android.view.SurfaceControl; import android.window.TransitionInfo; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -54,6 +56,7 @@ public class MixedTransitionHelper { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + "entering PIP while Split-Screen is foreground."); TransitionInfo.Change pipChange = null; + TransitionInfo.Change pipActivityChange = null; TransitionInfo.Change wallpaper = null; final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); @@ -68,6 +71,13 @@ public class MixedTransitionHelper { pipChange = change; // going backwards, so remove-by-index is fine. everythingElse.getChanges().remove(i); + } else if (change.getTaskInfo() == null && change.getParent() != null + && pipChange != null && change.getParent().equals(pipChange.getContainer())) { + // Cache the PiP activity if it's a target and cached pip task change is its parent; + // note that we are bottom-to-top, so if such activity has a task + // that is also a target, then it must have been cached already as pipChange. + pipActivityChange = change; + everythingElse.getChanges().remove(i); } else if (isHomeOpening(change)) { homeIsOpening = true; } else if (isWallpaper(change)) { @@ -138,9 +148,19 @@ public class MixedTransitionHelper { } } - pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); - pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, - finishCB); + if (PipUtils.isPip2ExperimentEnabled()) { + TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */); + pipInfo.getChanges().add(pipChange); + if (pipActivityChange != null) { + pipInfo.getChanges().add(pipActivityChange); + } + pipHandler.startAnimation(mixed.mTransition, pipInfo, startTransaction, + finishTransaction, finishCB); + } else { + pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); + pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, + finishCB); + } // make a new finishTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction(); 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 d5929f010e02..abfb41bb513a 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 @@ -39,6 +39,7 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived; import static com.android.window.flags.Flags.ensureWallpaperInTransitions; +import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -84,6 +85,7 @@ 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.desktopmode.DesktopModeTransitionTypes; +import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.FocusTransitionListener; @@ -795,6 +797,14 @@ public class Transitions implements RemoteCallable<Transitions>, mReadyDuringSync.remove(active); } + // If any of the changes are on DesktopWallpaperActivity, add the flag to the change. + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getTaskInfo() != null + && DesktopWallpaperActivity.isWallpaperTask(change.getTaskInfo())) { + change.setFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY); + } + } + final Track track = getOrCreateTrack(info.getTrack()); track.mReadyTransitions.add(active); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java index 0d75e659d95c..7948eadb28f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java @@ -110,9 +110,6 @@ public abstract class CarWindowDecorViewModel SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - if (!shouldShowWindowDecor(taskInfo)) { - return false; - } createWindowDecoration(taskInfo, taskSurface, startT, finishT); return true; } @@ -125,12 +122,9 @@ public abstract class CarWindowDecorViewModel return; } - if (!shouldShowWindowDecor(taskInfo)) { - destroyWindowDecoration(taskInfo); - return; - } - - decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + decoration.relayout(taskInfo, t, t, + /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo)); } @Override @@ -221,7 +215,8 @@ public abstract class CarWindowDecorViewModel mWindowDecorViewHostSupplier, new ButtonClickListener(taskInfo)); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - windowDecoration.relayout(taskInfo, startT, finishT); + windowDecoration.relayout(taskInfo, startT, finishT, + /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo)); } private class ButtonClickListener implements View.OnClickListener { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java index 1ca82d23c830..39437845301e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java @@ -20,14 +20,17 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.Context; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.View; +import android.view.WindowInsets; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -44,6 +47,7 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou private WindowDecorLinearLayout mRootView; private @ShellBackgroundThread final ShellExecutor mBgExecutor; private final View.OnClickListener mClickListener; + private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>(); CarWindowDecoration( Context context, @@ -71,26 +75,32 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou @SuppressLint("MissingPermission") void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true); + } + + @SuppressLint("MissingPermission") + void relayout(ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, + boolean isCaptionVisible) { final WindowContainerTransaction wct = new WindowContainerTransaction(); RelayoutParams relayoutParams = new RelayoutParams(); - RelayoutResult<WindowDecorLinearLayout> outResult = new RelayoutResult<>(); updateRelayoutParams(relayoutParams, taskInfo, - mDisplayController.getInsetsState(taskInfo.displayId)); + mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible); - relayout(relayoutParams, startT, finishT, wct, mRootView, outResult); + relayout(relayoutParams, startT, finishT, wct, mRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct)); - if (outResult.mRootView == null) { + if (mResult.mRootView == null) { // This means something blocks the window decor from showing, e.g. the task is hidden. // Nothing is set up in this case including the decoration surface. return; } - if (mRootView != outResult.mRootView) { - mRootView = outResult.mRootView; - setupRootView(outResult.mRootView, mClickListener); + if (mRootView != mResult.mRootView) { + mRootView = mResult.mRootView; + setupRootView(mResult.mRootView, mClickListener); } } @@ -108,18 +118,31 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou private void updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, - InsetsState displayInsetsState) { + @Nullable InsetsState displayInsetsState, + boolean isCaptionVisible) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; // todo(b/382071404): update to car specific UI relayoutParams.mLayoutResId = R.layout.caption_window_decor; relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; - relayoutParams.mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded; - relayoutParams.mCaptionTopPadding = 0; + relayoutParams.mIsCaptionVisible = + isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded; + if (displayInsetsState != null) { + relayoutParams.mCaptionTopPadding = getTopPadding( + taskInfo.getConfiguration().windowConfiguration.getBounds(), + displayInsetsState); + } relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; relayoutParams.mApplyStartTransactionOnDraw = true; } + private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) { + Insets systemDecor = insetsState.calculateInsets(taskBounds, + WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(), + false /* ignoreVisibility */); + return systemDecor.top; + } + /** * Sets up listeners when a new root view is created. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 055bc8f5f092..eb3a698fb58e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -34,6 +34,7 @@ import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HAND import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod; +import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; @@ -99,6 +100,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; import com.android.wm.shell.apptoweb.AssistContentRequester; +import com.android.wm.shell.common.ComponentUtils; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; @@ -979,7 +981,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent); } } else if (id == R.id.minimize_window) { - mDesktopTasksController.minimizeTask(decoration.mTaskInfo); + mDesktopTasksController.minimizeTask( + decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON); } } @@ -1926,14 +1929,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // instances, then refer to the list's size and reuse the list for Manage Windows menu. final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); try { + // TODO(b/389184897): Move the following into a helper method of + // RecentsTasksController, similar to #findTaskInBackground. + final String packageName = ComponentUtils.getPackageName(info); return activityTaskManager.getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_WITH_EXCLUDED, info.userId).getList().stream().filter( - recentTaskInfo -> (recentTaskInfo.taskId != info.taskId - && recentTaskInfo.baseActivity != null - && recentTaskInfo.baseActivity.getPackageName() - .equals(info.baseActivity.getPackageName()) - ) + recentTaskInfo -> { + if (recentTaskInfo.taskId == info.taskId) { + return false; + } + final String recentTaskPackageName = + ComponentUtils.getPackageName(recentTaskInfo); + return packageName != null + && packageName.equals(recentTaskPackageName); + } ).toList().size(); } catch (RemoteException e) { throw new RuntimeException(e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index 8dc921c986ce..07496eb0e526 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -31,6 +31,7 @@ import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import java.util.concurrent.TimeUnit @@ -69,6 +70,7 @@ class MultiDisplayVeiledResizeTaskPositioner( @DragPositioningCallback.CtrlType private var ctrlType = 0 private var isResizingOrAnimatingResize = false @Surface.Rotation private var rotation = 0 + private var startDisplayId = 0 constructor( taskOrganizer: ShellTaskOrganizer, @@ -95,6 +97,7 @@ class MultiDisplayVeiledResizeTaskPositioner( override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect { this.ctrlType = ctrlType + startDisplayId = displayId taskBoundsAtDragStart.set( desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds ) @@ -160,16 +163,47 @@ class MultiDisplayVeiledResizeTaskPositioner( interactionJankMonitor.begin( createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) ) + val t = transactionSupplier.get() - DragPositioningCallbackUtility.setPositionOnDrag( - desktopWindowDecoration, - repositionTaskBounds, - taskBoundsAtDragStart, - repositionStartPoint, - t, - x, - y, - ) + val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) + val currentDisplayLayout = displayController.getDisplayLayout(displayId) + + if (startDisplayLayout == null || currentDisplayLayout == null) { + // Fall back to single-display drag behavior if any display layout is unavailable. + DragPositioningCallbackUtility.setPositionOnDrag( + desktopWindowDecoration, + repositionTaskBounds, + taskBoundsAtDragStart, + repositionStartPoint, + t, + x, + y, + ) + } else { + val boundsDp = + MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag( + startDisplayLayout, + repositionStartPoint, + taskBoundsAtDragStart, + currentDisplayLayout, + x, + y, + ) + repositionTaskBounds.set( + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + boundsDp, + startDisplayLayout, + ) + ) + + // TODO(b/383069173): Render drag indicator(s) + + t.setPosition( + desktopWindowDecoration.mTaskSurface, + repositionTaskBounds.left.toFloat(), + repositionTaskBounds.top.toFloat(), + ) + } t.setFrameTimeline(Choreographer.getInstance().vsyncId) t.apply() } @@ -200,13 +234,38 @@ class MultiDisplayVeiledResizeTaskPositioner( } interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW) } else { - DragPositioningCallbackUtility.updateTaskBounds( - repositionTaskBounds, - taskBoundsAtDragStart, - repositionStartPoint, - x, - y, - ) + val startDisplayLayout = displayController.getDisplayLayout(startDisplayId) + val currentDisplayLayout = displayController.getDisplayLayout(displayId) + + if (startDisplayLayout == null || currentDisplayLayout == null) { + // Fall back to single-display drag behavior if any display layout is unavailable. + DragPositioningCallbackUtility.updateTaskBounds( + repositionTaskBounds, + taskBoundsAtDragStart, + repositionStartPoint, + x, + y, + ) + } else { + val boundsDp = + MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag( + startDisplayLayout, + repositionStartPoint, + taskBoundsAtDragStart, + currentDisplayLayout, + x, + y, + ) + repositionTaskBounds.set( + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + boundsDp, + currentDisplayLayout, + ) + ) + + // TODO(b/383069173): Clear drag indicator(s) + } + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt new file mode 100644 index 000000000000..bd924c2b47c1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt @@ -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.wm.shell.common + +import android.content.res.Configuration +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF +import android.testing.TestableResources +import com.android.wm.shell.ShellTestCase +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +/** + * Tests for [MultiDisplayDragMoveBoundsCalculator]. + * + * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveBoundsCalculatorTest + */ +class MultiDisplayDragMoveBoundsCalculatorTest : ShellTestCase() { + private lateinit var resources: TestableResources + + @Before + fun setUp() { + resources = mContext.getOrCreateTestableResources() + val configuration = Configuration() + configuration.uiMode = 0 + resources.overrideConfiguration(configuration) + } + + @Test + fun testCalculateGlobalDpBoundsForDrag() { + val repositionStartPoint = PointF(20f, 40f) + val boundsAtDragStart = Rect(10, 20, 110, 120) + val x = 300f + val y = 400f + val displayLayout0 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0, + MultiDisplayTestUtil.DISPLAY_DPI_0, + resources.resources, + ) + val displayLayout1 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + + val actualBoundsDp = + MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag( + displayLayout0, + repositionStartPoint, + boundsAtDragStart, + displayLayout1, + x, + y, + ) + + val expectedBoundsDp = RectF(240f, -820f, 340f, -720f) + assertEquals(expectedBoundsDp, actualBoundsDp) + } + + @Test + fun testConvertGlobalDpToLocalPxForRect() { + val displayLayout = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + val rectDp = RectF(150f, -350f, 300f, -250f) + + val actualBoundsPx = + MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect( + rectDp, + displayLayout, + ) + + val expectedBoundsPx = Rect(100, 1300, 400, 1500) + assertEquals(expectedBoundsPx, actualBoundsPx) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt new file mode 100644 index 000000000000..c8bebf11a82c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt @@ -0,0 +1,45 @@ +/* + * 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.common + +import android.content.res.Resources +import android.graphics.RectF +import android.util.DisplayMetrics +import android.view.DisplayInfo +import org.mockito.Mockito.spy + +/** Utility class for tests of [DesktopModeWindowDecorViewModel] */ +object MultiDisplayTestUtil { + // We have two displays, display#1 is placed on middle top of display#0: + // +---+ + // | 1 | + // +-+---+-+ + // | 0 | + // +-------+ + val DISPLAY_GLOBAL_BOUNDS_0 = RectF(0f, 0f, 1200f, 800f) + val DISPLAY_GLOBAL_BOUNDS_1 = RectF(100f, -1000f, 1100f, 0f) + val DISPLAY_DPI_0 = DisplayMetrics.DENSITY_DEFAULT + val DISPLAY_DPI_1 = DisplayMetrics.DENSITY_DEFAULT * 2 + + fun createSpyDisplayLayout(globalBounds: RectF, dpi: Int, resources: Resources): DisplayLayout { + val displayInfo = DisplayInfo() + displayInfo.logicalDensityDpi = dpi + val displayLayout = spy(DisplayLayout(displayInfo, resources, true, true)) + displayLayout.setGlobalBoundsDp(globalBounds) + return displayLayout + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt index 413e7bc5d1d6..016e04039b12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt @@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -294,7 +295,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { testExecutor.flushAll() assertThat(result).isTrue() - verify(desktopTasksController).minimizeTask(task) + verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE) } private fun setUpFreeformTask( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 4317143aebfe..a9ebcef9bd98 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -42,6 +42,7 @@ import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason @@ -62,6 +63,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE +import java.util.Optional import kotlin.test.assertFalse import kotlin.test.assertTrue import org.junit.Before @@ -69,6 +71,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -102,6 +105,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { private val mockShellInit = mock<ShellInit>() private val transitions = mock<Transitions>() private val context = mock<Context>() + private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + private val desktopTasksLimiter = mock<DesktopTasksLimiter>() private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver private lateinit var shellInit: ShellInit @@ -119,6 +124,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { mockShellInit, transitions, desktopModeEventLogger, + Optional.of(desktopTasksLimiter), + shellTaskOrganizer, ) val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) verify(mockShellInit) @@ -755,6 +762,39 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, never()).logSessionExit(any()) } + @Test + fun onTransitionReady_taskIsBeingMinimized_logsTaskMinimized() { + transitionObserver.isSessionActive = true + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1)) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + transitionObserver.addTaskInfosToCachedMap(taskInfo2) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_TO_BACK, 0) + .addChange(createChange(TRANSIT_TO_BACK, taskInfo2)) + .build() + `when`(desktopTasksLimiter.getMinimizingTask(any())) + .thenReturn( + DesktopTasksLimiter.TaskDetails( + taskInfo2.displayId, + taskInfo2.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskRemoved( + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + visibleTaskCount = 1, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + ) + } + /** Simulate calling the onTransitionReady() method */ private fun callOnTransitionReady(transitionInfo: TransitionInfo) { val transition = mock<IBinder>() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 6003a219d4db..8d73f3f59afd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -1046,14 +1046,14 @@ class DesktopRepositoryTest : ShellTestCase() { } @Test - fun removeDesktop_multipleTasks_removesAll() { + fun removeDesk_multipleTasks_removesAll() { // The front-most task will be the one added last through `addTask`. repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true) repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true) repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true) repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) - val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY) + val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY) assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder() assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 2b986d184c20..6c4f043a4f39 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -102,6 +102,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition @@ -257,6 +258,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() + private val SECONDARY_DISPLAY_ID = 1 private val DISPLAY_DIMENSION_SHORT = 1600 private val DISPLAY_DIMENSION_LONG = 2560 private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275) @@ -316,6 +318,8 @@ class DesktopTasksControllerTest : ShellTestCase() { val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECONDARY_DISPLAY_ID)) + .thenReturn(tda) whenever( mMockDesktopImmersiveController.exitImmersiveIfApplicable( any(), @@ -1143,6 +1147,21 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun launchIntent_taskInDesktopMode_transitionStarted() { + setUpLandscapeDisplay() + val freeformTask = setUpFreeformTask() + + controller.startLaunchIntentTransition( + freeformTask.baseIntent, + Bundle.EMPTY, + DEFAULT_DISPLAY, + ) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + assertThat(wct.hierarchyOps).hasSize(1) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { setUpLandscapeDisplay() @@ -2144,7 +2163,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(pipTask) + controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON) verifyExitDesktopWCTNotExecuted() taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false) @@ -2164,7 +2183,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2180,7 +2199,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(freeformTaskTransitionStarter).startPipTransition(any()) verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) @@ -2192,7 +2211,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(Binder()) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) @@ -2205,7 +2224,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startPipTransition(captor.capture()) @@ -2221,7 +2240,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2237,7 +2256,7 @@ class DesktopTasksControllerTest : ShellTestCase() { .thenReturn(transition) // The only active task is being minimized. - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2254,7 +2273,7 @@ class DesktopTasksControllerTest : ShellTestCase() { taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) // The only active task is already minimized. - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2271,7 +2290,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task1) + controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2291,7 +2310,7 @@ class DesktopTasksControllerTest : ShellTestCase() { taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) // task1 is the only visible task as task2 is minimized. - controller.minimizeTask(task1) + controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON) // Adds remove wallpaper operation val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2306,7 +2325,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any()) } @@ -2323,7 +2342,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit) ) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) assertThat(runOnTransit.invocations).isEqualTo(1) assertThat(runOnTransit.lastInvoked).isEqualTo(transition) @@ -3267,7 +3286,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(pipTask) + controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON) verifyExitDesktopWCTNotExecuted() freeformTask.isFocused = true @@ -3588,6 +3607,45 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG) + fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() { + val task = setUpFreeformTask() + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) + + val currentDragBounds = Rect(100, 200, 500, 1000) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) + whenever(motionEvent.displayId).thenReturn(SECONDARY_DISPLAY_ID) + + spyController.onDragPositioningEnd( + task, + mockSurface, + position = Point(100, 200), + inputCoordinate = PointF(200f, 300f), + currentDragBounds, + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), + motionEvent, + desktopWindowDecoration, + ) + + verify(transitions) + .startTransition( + eq(TRANSIT_CHANGE), + Mockito.argThat { wct -> + return@argThat wct.hierarchyOps[0].isReparent + }, + eq(null), + ) + } + + @Test fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() { val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) val spyController = spy(controller) @@ -5162,7 +5220,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val arg: ArgumentCaptor<WindowContainerTransaction> = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(desktopMixedTransitionHandler) - .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull()) + .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull()) return arg.value } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index c8214b3838e2..acfe1e9fd5a2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.graphics.Rect import android.os.Binder import android.os.Handler +import android.os.IBinder import android.os.UserManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags @@ -43,6 +44,7 @@ import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGAT import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer @@ -180,7 +182,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId) + addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -208,11 +210,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val taskTransition = Binder() val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.addPendingMinimizeChange( - pendingTransition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(pendingTransition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -231,11 +229,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() markTaskVisible(task) - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -254,11 +248,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -276,11 +266,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() { val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -299,11 +285,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val bounds = Rect(0, 0, 200, 200) val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply { @@ -330,11 +312,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val mergedTransition = Binder() val newTransition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - mergedTransition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(mergedTransition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() .onTransitionMerged(mergedTransition, newTransition) @@ -541,11 +519,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -573,11 +547,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -606,11 +576,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val mergedTransition = Binder() val newTransition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - mergedTransition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(mergedTransition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -633,6 +599,60 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) } + @Test + fun getMinimizingTask_noPendingTransition_returnsNull() { + val transition = Binder() + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull() + } + + @Test + fun getMinimizingTask_pendingTaskTransition_returnsTask() { + val transition = Binder() + val task = setUpFreeformTask() + addPendingMinimizeChange( + transition, + taskId = task.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)) + .isEqualTo( + createTaskDetails(taskId = task.taskId, minimizeReason = MinimizeReason.TASK_LIMIT) + ) + } + + @Test + fun getMinimizingTask_activeTaskTransition_returnsTask() { + val transition = Binder() + val task = setUpFreeformTask() + addPendingMinimizeChange( + transition, + taskId = task.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build() + + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + transition, + transitionInfo, + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), + ) + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)) + .isEqualTo( + createTaskDetails( + taskId = task.taskId, + transitionInfo = transitionInfo, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + } + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) `when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) @@ -640,6 +660,20 @@ class DesktopTasksLimiterTest : ShellTestCase() { return task } + private fun createTaskDetails( + displayId: Int = DEFAULT_DISPLAY, + taskId: Int, + transitionInfo: TransitionInfo? = null, + minimizeReason: MinimizeReason? = null, + ) = DesktopTasksLimiter.TaskDetails(displayId, taskId, transitionInfo, minimizeReason) + + fun addPendingMinimizeChange( + transition: IBinder, + displayId: Int = DEFAULT_DISPLAY, + taskId: Int, + minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT, + ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason) + private fun markTaskVisible(task: RunningTaskInfo) { desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java index 9cc18ffdaed7..607e6a450883 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java @@ -19,6 +19,8 @@ package com.android.wm.shell.pip2.animation; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -33,6 +35,7 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.R; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import org.junit.Before; @@ -48,6 +51,8 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) public class PipAlphaAnimatorTest { + private static final float TEST_CORNER_RADIUS = 1f; + private static final float TEST_SHADOW_RADIUS = 2f; @Mock private Context mMockContext; @@ -55,7 +60,9 @@ public class PipAlphaAnimatorTest { @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; - @Mock private SurfaceControl.Transaction mMockTransaction; + @Mock private SurfaceControl.Transaction mMockAnimateTransaction; + @Mock private SurfaceControl.Transaction mMockStartTransaction; + @Mock private SurfaceControl.Transaction mMockFinishTransaction; @Mock private Runnable mMockStartCallback; @@ -69,9 +76,15 @@ public class PipAlphaAnimatorTest { MockitoAnnotations.initMocks(this); when(mMockContext.getResources()).thenReturn(mMockResources); when(mMockResources.getInteger(anyInt())).thenReturn(0); - when(mMockFactory.getTransaction()).thenReturn(mMockTransaction); - when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) - .thenReturn(mMockTransaction); + when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction); + when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius)) + .thenReturn((int) TEST_CORNER_RADIUS); + when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius)) + .thenReturn((int) TEST_SHADOW_RADIUS); + + prepareTransaction(mMockAnimateTransaction); + prepareTransaction(mMockStartTransaction); + prepareTransaction(mMockFinishTransaction); mTestLeash = new SurfaceControl.Builder() .setContainerLayer() @@ -82,8 +95,8 @@ public class PipAlphaAnimatorTest { @Test public void setAnimationStartCallback_fadeInAnimator_callbackStartCallback() { - mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction, - PipAlphaAnimator.FADE_IN); + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_IN); mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback); mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback); @@ -98,8 +111,8 @@ public class PipAlphaAnimatorTest { @Test public void setAnimationEndCallback_fadeInAnimator_callbackStartAndEndCallback() { - mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction, - PipAlphaAnimator.FADE_IN); + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_IN); mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback); mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback); @@ -109,36 +122,98 @@ public class PipAlphaAnimatorTest { }); verify(mMockStartCallback).run(); - verify(mMockStartCallback).run(); + verify(mMockEndCallback).run(); + } + + @Test + public void onAnimationStart_setCornerAndShadowRadii() { + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_IN); + mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipAlphaAnimator.start(); + mPipAlphaAnimator.pause(); + }); + + verify(mMockStartTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockStartTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); + } + + @Test + public void onAnimationUpdate_setCornerAndShadowRadii() { + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_IN); + mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipAlphaAnimator.start(); + mPipAlphaAnimator.pause(); + }); + + verify(mMockAnimateTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockAnimateTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); + } + + @Test + public void onAnimationEnd_setCornerAndShadowRadii() { + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_IN); + mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipAlphaAnimator.start(); + mPipAlphaAnimator.end(); + }); + + verify(mMockFinishTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockFinishTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); } @Test public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() { - mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction, - PipAlphaAnimator.FADE_IN); + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_IN); mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mPipAlphaAnimator.start(); - clearInvocations(mMockTransaction); + clearInvocations(mMockAnimateTransaction); mPipAlphaAnimator.end(); }); - verify(mMockTransaction).setAlpha(mTestLeash, 1.0f); + verify(mMockAnimateTransaction).setAlpha(mTestLeash, 1.0f); } @Test public void onAnimationEnd_fadeOutAnimator_leashInvisibleAtEnd() { - mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction, - PipAlphaAnimator.FADE_OUT); + mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction, + mMockFinishTransaction, PipAlphaAnimator.FADE_OUT); mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mPipAlphaAnimator.start(); - clearInvocations(mMockTransaction); + clearInvocations(mMockAnimateTransaction); mPipAlphaAnimator.end(); }); - verify(mMockTransaction).setAlpha(mTestLeash, 0f); + verify(mMockAnimateTransaction).setAlpha(mTestLeash, 0f); + } + + + // set up transaction chaining + private void prepareTransaction(SurfaceControl.Transaction tx) { + when(tx.setAlpha(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); + when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); + when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index aef44a40fa0f..bd857c7dcd45 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -111,7 +111,7 @@ public class PipSchedulerTest { mRootTaskDisplayAreaOrganizer); mPipScheduler.setPipTransitionController(mMockPipTransitionController); mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory); - mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) -> + mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) -> mMockAlphaAnimator); SurfaceControl testLeash = new SurfaceControl.Builder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 7e5d6ce38c5a..28f4ea0c7ada 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -22,13 +22,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM; import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN; import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -65,8 +65,8 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.view.SurfaceControl; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; @@ -253,7 +253,6 @@ public class RecentTasksControllerTest extends ShellTestCase { t3.taskId, -1); } - @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) @Test public void testGetRecentTasks_removesDesktopWallpaperActivity() { RecentTaskInfo t1 = makeTaskInfo(1); @@ -753,15 +752,9 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to set the raw task list on the controller. */ - private ArrayList<RecentTaskInfo> setRawList( - RecentTaskInfo... tasks) { - ArrayList<RecentTaskInfo> rawList = new ArrayList<>(); - for (RecentTaskInfo task : tasks) { - rawList.add(task); - } - doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), + private void setRawList(RecentTaskInfo... tasks) { + doReturn(Arrays.asList(tasks)).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), anyInt()); - return rawList; } /** @@ -794,8 +787,9 @@ public class RecentTasksControllerTest extends ShellTestCase { assertNull(pair.getSplitBounds()); } } - assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds) + " Received: " + Arrays.toString(flattenedTaskIds), - Arrays.equals(flattenedTaskIds, expectedTaskIds)); + flattenedTaskIds, + expectedTaskIds); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index dd645fd968e4..0a19be4eb959 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1781,6 +1781,7 @@ public class ShellTransitionTests extends ShellTestCase { taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); taskInfo.configuration.windowConfiguration.setActivityType(activityType); taskInfo.token = mock(WindowContainerToken.class); + taskInfo.baseIntent = mock(Intent.class); return taskInfo; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 79e9b9c8cd77..b4791642663a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -61,6 +61,7 @@ import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.desktopmode.DesktopImmersiveController import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction @@ -272,7 +273,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest onClickListenerCaptor.value.onClick(view) - verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo) + verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index f179cac32244..2207c705d7dc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -17,14 +17,14 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager import android.app.WindowConfiguration -import android.content.Context -import android.content.res.Resources +import android.content.res.Configuration import android.graphics.Point import android.graphics.Rect import android.os.Handler import android.os.IBinder import android.os.Looper import android.testing.AndroidTestingRunner +import android.testing.TestableResources import android.view.Display import android.view.Surface.ROTATION_0 import android.view.Surface.ROTATION_270 @@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.MultiDisplayTestUtil import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionFinishCallback import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM @@ -55,6 +56,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.argThat +import org.mockito.Mockito.doAnswer import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -82,7 +84,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Mock private lateinit var taskBinder: IBinder @Mock private lateinit var mockDisplayController: DisplayController - @Mock private lateinit var mockDisplayLayout: DisplayLayout @Mock private lateinit var mockDisplay: Display @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction> @Mock private lateinit var mockTransaction: SurfaceControl.Transaction @@ -90,9 +91,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Mock private lateinit var mockTransitionInfo: TransitionInfo @Mock private lateinit var mockFinishCallback: TransitionFinishCallback @Mock private lateinit var mockTransitions: Transitions - @Mock private lateinit var mockContext: Context - @Mock private lateinit var mockResources: Resources @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + private lateinit var resources: TestableResources + private lateinit var spyDisplayLayout0: DisplayLayout + private lateinit var spyDisplayLayout1: DisplayLayout + private val mainHandler = Handler(Looper.getMainLooper()) private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner @@ -101,24 +104,45 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - mockDesktopWindowDecoration.mDisplay = mockDisplay - mockDesktopWindowDecoration.mDecorWindowContext = mockContext - whenever(mockContext.getResources()).thenReturn(mockResources) whenever(taskToken.asBinder()).thenReturn(taskBinder) - whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) - whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> - if ( - mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration - .displayRotation == ROTATION_90 || + mockDesktopWindowDecoration.mDisplay = mockDisplay + mockDesktopWindowDecoration.mDecorWindowContext = mContext + resources = mContext.orCreateTestableResources + val resourceConfiguration = Configuration() + resourceConfiguration.uiMode = 0 + resources.overrideConfiguration(resourceConfiguration) + spyDisplayLayout0 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0, + MultiDisplayTestUtil.DISPLAY_DPI_0, + resources.resources, + ) + spyDisplayLayout1 = + MultiDisplayTestUtil.createSpyDisplayLayout( + MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1, + MultiDisplayTestUtil.DISPLAY_DPI_1, + resources.resources, + ) + whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_0)).thenReturn(spyDisplayLayout0) + whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_1)).thenReturn(spyDisplayLayout1) + whenever(spyDisplayLayout0.densityDpi()).thenReturn(DENSITY_DPI) + whenever(spyDisplayLayout1.densityDpi()).thenReturn(DENSITY_DPI) + doAnswer { i -> + val rect = i.getArgument<Rect>(0) + if ( mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration - .displayRotation == ROTATION_270 - ) { - (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE) - } else { - (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT) + .displayRotation == ROTATION_90 || + mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration + .displayRotation == ROTATION_270 + ) { + rect.set(STABLE_BOUNDS_LANDSCAPE) + } else { + rect.set(STABLE_BOUNDS_PORTRAIT) + } + null } - } + .`when`(spyDisplayLayout0) + .getStableBounds(any()) `when`(mockTransactionFactory.get()).thenReturn(mockTransaction) mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { @@ -127,14 +151,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { minWidth = MIN_WIDTH minHeight = MIN_HEIGHT defaultMinSize = DEFAULT_MIN - displayId = DISPLAY_ID + displayId = DISPLAY_ID_0 configuration.windowConfiguration.setBounds(STARTING_BOUNDS) configuration.windowConfiguration.displayRotation = ROTATION_90 isResizeable = true } `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA) mockDesktopWindowDecoration.mDisplay = mockDisplay - whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID_0 } taskPositioner = MultiDisplayVeiledResizeTaskPositioner( @@ -153,14 +177,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -185,13 +209,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() + 60, STARTING_BOUNDS.top.toFloat() + 100, ) @@ -205,7 +229,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { val endBounds = taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() + 70, STARTING_BOUNDS.top.toFloat() + 20, ) @@ -221,16 +245,39 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } @Test + fun testDragResize_movesTaskToNewDisplay() = runOnUiThread { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, + DISPLAY_ID_0, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat(), + ) + + taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 1900f) + + val rectAfterMove = Rect(200, -50, 300, 50) + verify(mockTransaction) + .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat())) + + val endBounds = taskPositioner.onDragPositioningEnd(DISPLAY_ID_1, 300f, 450f) + val rectAfterEnd = Rect(300, 450, 500, 650) + + verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) + verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + Assert.assertEquals(rectAfterEnd, endBounds) + } + + @Test fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.right.toFloat() + 10, STARTING_BOUNDS.top.toFloat() + 10, ) @@ -252,7 +299,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { ) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.right.toFloat() + 20, STARTING_BOUNDS.top.toFloat() + 20, ) @@ -278,20 +325,20 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( - DISPLAY_ID, + DISPLAY_ID_0, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() + 10, STARTING_BOUNDS.top.toFloat() + 10, ) @@ -326,16 +373,16 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) val newX = STARTING_BOUNDS.left.toFloat() + 5 val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 - taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY) + taskPositioner.onDragPositioningMove(DISPLAY_ID_0, newX, newY) - taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY) + taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, newX, newY) verify(mockShellTaskOrganizer, never()) .applyTransaction( @@ -354,7 +401,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -375,7 +422,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration.mHasGlobalFocus = true taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -396,7 +443,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { mockDesktopWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -427,7 +474,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { rectAfterDrag.right += 2000 rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom // First drag; we should fetch stable bounds. - verify(mockDisplayLayout, times(1)).getStableBounds(any()) + verify(spyDisplayLayout0, times(1)).getStableBounds(any()) verify(mockTransitions) .startTransition( eq(TRANSIT_CHANGE), @@ -451,7 +498,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { ) // Display did not rotate; we should use previous stable bounds - verify(mockDisplayLayout, times(1)).getStableBounds(any()) + verify(spyDisplayLayout0, times(1)).getStableBounds(any()) // Rotate the screen to portrait mockDesktopWindowDecoration.mTaskInfo.apply { @@ -482,7 +529,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { eq(taskPositioner), ) // Display has rotated; we expect a new stable bounds. - verify(mockDisplayLayout, times(2)).getStableBounds(any()) + verify(spyDisplayLayout0, times(2)).getStableBounds(any()) } @Test @@ -491,13 +538,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) taskPositioner.onDragPositioningMove( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20, ) @@ -507,7 +554,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID)) taskPositioner.onDragPositioningEnd( - DISPLAY_ID, + DISPLAY_ID_0, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), ) @@ -568,10 +615,10 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) { - taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY) - taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY) + taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID_0, startX, startY) + taskPositioner.onDragPositioningMove(DISPLAY_ID_0, endX, endY) - taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY) + taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, endX, endY) } companion object { @@ -580,7 +627,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { private const val MIN_HEIGHT = 10 private const val DENSITY_DPI = 20 private const val DEFAULT_MIN = 40 - private const val DISPLAY_ID = 1 + private const val DISPLAY_ID_0 = 0 + private const val DISPLAY_ID_1 = 1 private const val NAVBAR_HEIGHT = 50 private const val CAPTION_HEIGHT = 50 private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp index 7eedfdb5c921..b80c8755f23e 100644 --- a/libs/androidfw/ApkParsing.cpp +++ b/libs/androidfw/ApkParsing.cpp @@ -33,7 +33,7 @@ const size_t LIB_SUFFIX_LEN = LIB_SUFFIX.size(); static const std::array<std::string_view, 2> abis = {"arm64-v8a", "x86_64"}; namespace android::util { -const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, bool debuggable) { +const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit) { // Make sure the filename is at least to the minimum library name size. const size_t fileNameLen = strlen(fileName); static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN; @@ -66,14 +66,6 @@ const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, return nullptr; } - if (!debuggable) { - // Make sure the filename starts with lib and ends with ".so". - if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX.data(), LIB_SUFFIX_LEN) != 0 - || strncmp(lastSlash, LIB_PREFIX.data(), LIB_PREFIX_LEN) != 0) { - return nullptr; - } - } - // Don't include 64 bit versions if they are suppressed if (suppress64Bit && std::find(abis.begin(), abis.end(), std::string_view( fileName + APK_LIB_LEN, lastSlash - fileName - APK_LIB_LEN)) != abis.end()) { diff --git a/libs/androidfw/include/androidfw/ApkParsing.h b/libs/androidfw/include/androidfw/ApkParsing.h index 194eaae8e12a..b288e152f383 100644 --- a/libs/androidfw/include/androidfw/ApkParsing.h +++ b/libs/androidfw/include/androidfw/ApkParsing.h @@ -24,7 +24,7 @@ extern const size_t APK_LIB_LEN; namespace android::util { // Checks if filename is a valid library path and returns a pointer to the last slash in the path // if it is, nullptr otherwise -const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit, bool debuggable); +const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit); // Equivalent to android.os.FileUtils.isFilenameSafe bool isFilenameSafe(const char* filename); diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp index ac1dc9b88463..f1f9d7166914 100644 --- a/libs/androidfw/tests/ApkParsing_test.cpp +++ b/libs/androidfw/tests/ApkParsing_test.cpp @@ -27,57 +27,45 @@ using ::testing::NotNull; namespace android { TEST(ApkParsingTest, ValidArm64Path) { const char* path = "lib/arm64-v8a/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, NotNull()); ASSERT_THAT(lastSlash, Eq(path + 13)); } TEST(ApkParsingTest, ValidArm64PathButSuppressed) { const char* path = "lib/arm64-v8a/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, true, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, true); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, ValidArm32Path) { const char* path = "lib/armeabi-v7a/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, NotNull()); ASSERT_THAT(lastSlash, Eq(path + 15)); } -TEST(ApkParsingTest, InvalidMustStartWithLib) { - const char* path = "lib/arm64-v8a/random.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); - ASSERT_THAT(lastSlash, IsNull()); -} - -TEST(ApkParsingTest, InvalidMustEndInSo) { - const char* path = "lib/arm64-v8a/library.txt"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); - ASSERT_THAT(lastSlash, IsNull()); -} - TEST(ApkParsingTest, InvalidCharacter) { const char* path = "lib/arm64-v8a/lib#.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, InvalidSubdirectories) { const char* path = "lib/arm64-v8a/anything/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, InvalidFileAtRoot) { const char* path = "lib/library.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } TEST(ApkParsingTest, InvalidPrefix) { const char* path = "assets/libhello.so"; - auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false); + auto lastSlash = util::ValidLibraryPathLastSlash(path, false); ASSERT_THAT(lastSlash, IsNull()); } }
\ No newline at end of file diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 69fe40c755ea..6ab8e4e0e2ab 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -323,6 +323,7 @@ void RenderThread::initGrContextOptions(GrContextOptions& options) { } void RenderThread::destroyRenderingContext() { + ATRACE_CALL(); mFunctorManager.onContextDestroyed(); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { if (mEglManager->hasEglContext()) { @@ -520,7 +521,10 @@ void RenderThread::preload() { // EGL driver is always preloaded only if HWUI renders with GL. if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { if (Properties::earlyPreloadGlContext()) { - queue().post([this]() { requireGlContext(); }); + queue().post([this]() { + ATRACE_NAME("earlyPreloadGlContext"); + requireGlContext(); + }); } else { std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); }); eglInitThread.detach(); @@ -528,9 +532,6 @@ void RenderThread::preload() { } else { requireVkContext(); } - if (Properties::earlyPreloadGlContext()) { - queue().post([]() { GraphicBufferAllocator::getInstance(); }); - } HardwareBitmapUploader::initialize(); } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index aeb028ccd0a6..b7269256a449 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -18,6 +18,7 @@ package android.media.quality; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -35,6 +36,8 @@ import androidx.annotation.RequiresPermission; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -74,6 +77,39 @@ public final class MediaQualityManager { */ public static final String OPTION_INCLUDE_PARAMETERS = "include_parameters"; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED, + AMBIENT_BACKLIGHT_EVENT_METADATA, + AMBIENT_BACKLIGHT_EVENT_INTERRUPTED}) + public @interface AmbientBacklightEventTypes {} + + /** + * Event type for ambient backlight events. The ambient backlight is enabled. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1; + + /** + * Event type for ambient backlight events. The ambient backlight is disabled. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2; + + /** + * Event type for ambient backlight events. The ambient backlight metadata is + * available. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3; + + /** + * Event type for ambient backlight events. The ambient backlight event is + * preempted by another application. + * @hide + */ + public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4; + /** * @hide diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml new file mode 100644 index 000000000000..ec9ee2211259 --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2025 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="@color/settingslib_materialColorSurface"/> + <item android:state_checked="true" android:color="?attr/colorContainerChecked"/> + <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/> + <item android:color="@color/settingslib_materialColorPrimaryContainer" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml new file mode 100644 index 000000000000..0488cbaead22 --- /dev/null +++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2025 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface"/> + <item android:state_checkable="true" android:state_checked="true" + android:color="?attr/colorOnContainerChecked"/> + <item android:state_checkable="true" android:color="?attr/colorOnContainerUnchecked"/> + <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml index fd8cecb8536e..267c9f65e104 100644 --- a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml +++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml @@ -17,9 +17,14 @@ <resources> <style name="SettingsLibActionButton.Expressive" parent="SettingsLibButtonStyle.Expressive.Tonal"> - <item name="android:backgroundTint">@color/settingslib_materialColorPrimaryContainer</item> - <item name="iconTint">@color/settingslib_materialColorOnPrimaryContainer</item> - <item name="iconGravity">textTop</item> + <item name="android:backgroundTint">@color/settingslib_expressive_actionbutton_background</item> + <item name="android:textColor">@color/settingslib_expressive_actionbutton_content_color</item> + <item name="android:insetTop">@dimen/settingslib_expressive_space_none</item> + <item name="android:insetBottom">@dimen/settingslib_expressive_space_none</item> + <item name="iconTint">@color/settingslib_expressive_actionbutton_content_color</item> + <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> + <item name="iconPadding">@dimen/settingslib_expressive_space_none</item>" + <item name="iconGravity">textStart</item> </style> <style name="SettingsLibActionButton.Expressive.Label" parent=""> diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java index 601e001f48c2..0027d632319b 100644 --- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java +++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java @@ -549,7 +549,7 @@ public class ActionButtonsPreference extends Preference implements GroupSectionD ((MaterialButton) mButton).setIcon(mIcon); } mButton.setEnabled(mIsEnabled); - mActionLayout.setOnClickListener(mListener); + mButton.setOnClickListener(mListener); mActionLayout.setEnabled(mIsEnabled); mActionLayout.setContentDescription(mText); } else { diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 933c512313ce..e5b58370e6dd 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -46,6 +46,8 @@ android_library { "SettingsLibIntroPreference", "SettingsLibLayoutPreference", "SettingsLibMainSwitchPreference", + "SettingsLibMetadata", + "SettingsLibPreference", "SettingsLibProfileSelector", "SettingsLibProgressBar", "SettingsLibRestrictedLockUtils", @@ -77,6 +79,7 @@ android_library { "src/**/*.kt", "src/**/I*.aidl", ], + kotlincflags: ["-Xjvm-default=all"], } // defaults for lint option diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 8b29b0044eea..ce66a360a99f 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -40,7 +40,6 @@ import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget import com.android.settingslib.graph.proto.PreferenceScreenProto import com.android.settingslib.graph.proto.TextProto -import com.android.settingslib.metadata.FloatPersistentPreference import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceHierarchy @@ -410,13 +409,13 @@ fun PreferenceMetadata.toProto( value = preferenceValueProto { when (metadata) { is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it } - is FloatPersistentPreference -> - storage.getFloat(metadata.key)?.let { floatValue = it } else -> {} } when (metadata.valueType) { Boolean::class.javaObjectType -> storage.getBoolean(metadata.key)?.let { booleanValue = it } + Float::class.javaObjectType -> + storage.getFloat(metadata.key)?.let { floatValue = it } } } } @@ -428,12 +427,12 @@ fun PreferenceMetadata.toProto( max = metadata.getMaxValue(context) step = metadata.getIncrementStep(context) } - is FloatPersistentPreference -> floatType = true else -> {} } if (metadata is PersistentPreference<*>) { when (metadata.valueType) { Boolean::class.javaObjectType -> booleanType = true + Float::class.javaObjectType -> floatType = true } } } diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt new file mode 100644 index 000000000000..7323488c5299 --- /dev/null +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 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.settingslib.metadata + +/** Metrics logger for preference actions triggered by user interaction. */ +interface PreferenceUiActionMetricsLogger { + + /** + * Logs preference value change due to user interaction. + * + * Note: Preference value changed by external Set is excluded. + */ + fun logPreferenceValueChange( + screen: PreferenceScreenMetadata, + preference: PreferenceMetadata, + value: Any?, + ) {} +} + +/** Metrics logger for preference remote operations (e.g. external get/set). */ +interface PreferenceRemoteOpMetricsLogger diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt index 4cc65815a78a..8e850465ef7d 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt @@ -219,12 +219,3 @@ interface RangeValue : ValueDescriptor { override fun isValidValue(context: Context, index: Int) = index in getMinValue(context)..getMaxValue(context) } - -/** A persistent preference that has a boolean value. */ -interface BooleanPreference : PersistentPreference<Boolean> { - override val valueType: Class<Boolean> - get() = Boolean::class.javaObjectType -} - -/** A persistent preference that has a float value. */ -interface FloatPersistentPreference : PersistentPreference<Float> diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt index 9fc21343b6a0..c74b3151abb2 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt @@ -32,6 +32,9 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { */ var preferenceScreenMetadataFactories = FixedArrayMap<String, PreferenceScreenMetadataFactory>() + /** Metrics logger for preference actions triggered by user interaction. */ + var preferenceUiActionMetricsLogger: PreferenceUiActionMetricsLogger? = null + private var readWritePermitProvider: ReadWritePermitProvider = object : ReadWritePermitProvider {} diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt index b79a0c4f6381..623ea22410e5 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt @@ -18,8 +18,20 @@ package com.android.settingslib.metadata import androidx.annotation.StringRes +/** A persistent preference that has a boolean value. */ +interface BooleanValuePreference : PersistentPreference<Boolean> { + override val valueType: Class<Boolean> + get() = Boolean::class.javaObjectType +} + +/** A persistent preference that has a float value. */ +interface FloatValuePreference : PersistentPreference<Float> { + override val valueType: Class<Float> + get() = Float::class.javaObjectType +} + /** Common base class for preferences that have two selectable states and save a boolean value. */ -interface TwoStatePreference : PreferenceMetadata, BooleanPreference +interface TwoStatePreference : PreferenceMetadata, BooleanValuePreference /** A preference that provides a two-state toggleable option. */ open class SwitchPreference diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt index 6b7be91c1903..c61c6a5c75fa 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt @@ -17,6 +17,7 @@ package com.android.settingslib.preference import android.content.Context +import androidx.annotation.CallSuper import androidx.preference.DialogPreference import androidx.preference.ListPreference import androidx.preference.Preference @@ -59,6 +60,7 @@ interface PreferenceBinding { * @param preference preference widget created by [createWidget] * @param metadata metadata to apply */ + @CallSuper fun bind(preference: Preference, metadata: PreferenceMetadata) { metadata.apply { preference.key = key diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt index bd5d17cb2468..b82c554ea26a 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt @@ -45,7 +45,7 @@ interface PreferenceScreenBinding : PreferenceBinding { context.getString(screenTitle) } else { screenMetadata.getScreenTitle(context) - ?: (this as? PreferenceTitleProvider)?.getTitle(context) + ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context) } } } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreDelegate.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreDelegate.kt new file mode 100644 index 000000000000..482eaf9146e5 --- /dev/null +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreDelegate.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2025 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.settingslib.preference + +import androidx.preference.PreferenceDataStore + +/** [PreferenceDataStore] delegate. */ +open class PreferenceDataStoreDelegate(internal val delegate: PreferenceDataStore) : + PreferenceDataStore() { + + override fun getBoolean(key: String, defValue: Boolean): Boolean = + delegate.getBoolean(key, defValue) + + override fun getFloat(key: String, defValue: Float): Float = delegate.getFloat(key, defValue) + + override fun getInt(key: String, defValue: Int): Int = delegate.getInt(key, defValue) + + override fun getLong(key: String, defValue: Long): Long = delegate.getLong(key, defValue) + + override fun getString(key: String, defValue: String?): String? = + delegate.getString(key, defValue) + + override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? = + delegate.getStringSet(key, defValues) + + override fun putBoolean(key: String, value: Boolean) = delegate.putBoolean(key, value) + + override fun putFloat(key: String, value: Float) = delegate.putFloat(key, value) + + override fun putInt(key: String, value: Int) = delegate.putInt(key, value) + + override fun putLong(key: String, value: Long) = delegate.putLong(key, value) + + override fun putString(key: String, value: String?) = delegate.putString(key, value) + + override fun putStringSet(key: String, values: Set<String>?) = + delegate.putStringSet(key, values) +} diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt index e237a6a4cf14..ffe181d0c350 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import android.util.Log import androidx.annotation.XmlRes +import androidx.lifecycle.Lifecycle import androidx.preference.PreferenceScreen import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider @@ -38,6 +39,11 @@ open class PreferenceFragment : preferenceScreen = createPreferenceScreen() } + override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) { + super.setPreferenceScreen(preferenceScreen) + updateActivityTitle() + } + fun createPreferenceScreen(): PreferenceScreen? = createPreferenceScreen(PreferenceScreenFactory(this)) @@ -102,9 +108,19 @@ open class PreferenceFragment : override fun onResume() { super.onResume() + // Even when activity has several fragments with preference screen, this will keep activity + // title in sync when fragment manager pops back stack. + updateActivityTitle() preferenceScreenBindingHelper?.onResume() } + internal fun updateActivityTitle() { + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return + val activity = activity ?: return + val title = preferenceScreen?.title ?: return + if (activity.title != title) activity.title = title + } + override fun onPause() { preferenceScreenBindingHelper?.onPause() super.onPause() diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt index 657f69a738f2..f3f854c0dc47 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt @@ -18,15 +18,30 @@ package com.android.settingslib.preference import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceGroup +import androidx.preference.PreferenceScreen import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceHierarchy +import com.android.settingslib.metadata.PreferenceScreenMetadata /** Inflates [PreferenceHierarchy] into given [PreferenceGroup] recursively. */ -fun PreferenceGroup.inflatePreferenceHierarchy( +fun PreferenceScreen.inflatePreferenceHierarchy( preferenceBindingFactory: PreferenceBindingFactory, hierarchy: PreferenceHierarchy, - storages: MutableMap<KeyValueStore, PreferenceDataStore> = mutableMapOf(), +) = + inflatePreferenceHierarchy( + hierarchy.metadata as PreferenceScreenMetadata, + preferenceBindingFactory, + hierarchy, + mutableMapOf(), + ) + +/** Inflates [PreferenceHierarchy] into given [PreferenceGroup] recursively. */ +private fun PreferenceGroup.inflatePreferenceHierarchy( + preferenceScreenMetadata: PreferenceScreenMetadata, + preferenceBindingFactory: PreferenceBindingFactory, + hierarchy: PreferenceHierarchy, + storages: MutableMap<KeyValueStore, PreferenceDataStore>, ) { preferenceBindingFactory.bind(this, hierarchy) hierarchy.forEach { @@ -38,11 +53,18 @@ fun PreferenceGroup.inflatePreferenceHierarchy( val preferenceGroup = widget as PreferenceGroup // MUST add preference before binding, otherwise exception is raised when add child addPreference(preferenceGroup) - preferenceGroup.inflatePreferenceHierarchy(preferenceBindingFactory, it) + preferenceGroup.inflatePreferenceHierarchy( + preferenceScreenMetadata, + preferenceBindingFactory, + it, + storages, + ) } else { (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage -> widget.preferenceDataStore = - storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) } + storages.getOrPut(storage) { + storage.toPreferenceDataStore(preferenceScreenMetadata, metadata) + } } preferenceBindingFactory.bind(widget, it, preferenceBinding) // MUST add preference after binding for persistent preference to get initial value diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt index 8358ab921fb6..4a6a589cd3c9 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt @@ -38,6 +38,7 @@ import com.android.settingslib.metadata.PreferenceHierarchyNode import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadata import com.android.settingslib.metadata.PreferenceScreenRegistry import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMultimap @@ -51,7 +52,7 @@ import com.google.common.collect.ImmutableMultimap */ class PreferenceScreenBindingHelper( context: Context, - fragment: PreferenceFragment, + private val fragment: PreferenceFragment, private val preferenceBindingFactory: PreferenceBindingFactory, private val preferenceScreen: PreferenceScreen, private val preferenceHierarchy: PreferenceHierarchy, @@ -70,9 +71,7 @@ class PreferenceScreenBindingHelper( override fun <T : Any> requirePreference(key: String) = findPreference<T>(key)!! override fun getKeyValueStore(key: String) = - (findPreference<Preference>(key)?.preferenceDataStore - as? PreferenceDataStoreAdapter) - ?.keyValueStore + findPreference<Preference>(key)?.preferenceDataStore?.findKeyValueStore() override fun notifyPreferenceChange(key: String) = notifyChange(key, PreferenceChangeReason.STATE) @@ -137,22 +136,29 @@ class PreferenceScreenBindingHelper( addObserver(preferenceObserver, mainExecutor) preferenceScreen.forEachRecursively { - val preferenceDataStore = it.preferenceDataStore - if (preferenceDataStore is PreferenceDataStoreAdapter) { + it.preferenceDataStore?.findKeyValueStore()?.let { keyValueStore -> val key = it.key - val keyValueStore = preferenceDataStore.keyValueStore storages[key] = keyValueStore keyValueStore.addObserver(key, storageObserver, mainExecutor) } } } + private fun PreferenceDataStore.findKeyValueStore(): KeyValueStore? = + when (this) { + is PreferenceDataStoreAdapter -> keyValueStore + is PreferenceDataStoreDelegate -> delegate.findKeyValueStore() + else -> null + } + private fun onPreferenceChange(key: String?, reason: Int) { if (key == null) return // bind preference to update UI preferenceScreen.findPreference<Preference>(key)?.let { - preferences[key]?.let { node -> preferenceBindingFactory.bind(it, node) } + val node = preferences[key] ?: return@let + preferenceBindingFactory.bind(it, node) + if (it == preferenceScreen) fragment.updateActivityTitle() } // check reason to avoid potential infinite loop @@ -239,17 +245,17 @@ class PreferenceScreenBindingHelper( preferenceBindingFactory: PreferenceBindingFactory, preferenceHierarchy: PreferenceHierarchy, ) { + val preferenceScreenMetadata = preferenceHierarchy.metadata as PreferenceScreenMetadata val preferences = mutableMapOf<String, PreferenceHierarchyNode>() - preferenceHierarchy.forEachRecursively { - val metadata = it.metadata - preferences[metadata.key] = it - } + preferenceHierarchy.forEachRecursively { preferences[it.metadata.key] = it } val storages = mutableMapOf<KeyValueStore, PreferenceDataStore>() fun Preference.setPreferenceDataStore(metadata: PreferenceMetadata) { (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage -> preferenceDataStore = - storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) } + storages.getOrPut(storage) { + storage.toPreferenceDataStore(preferenceScreenMetadata, metadata) + } } } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt index 2e7221bd4d7f..f5ab4b2e38d8 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt @@ -17,7 +17,12 @@ package com.android.settingslib.preference import androidx.preference.Preference +import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceGroup +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadata +import com.android.settingslib.metadata.PreferenceScreenRegistry /** Traversals preference hierarchy recursively and applies an action. */ fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) { @@ -31,3 +36,51 @@ fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) { } } } + +/** + * Converts [KeyValueStore] to [PreferenceDataStore]. + * + * [PreferenceScreenRegistry.preferenceUiActionMetricsLogger] is wrapped on top of + * [PreferenceDataStoreDelegate] to log metrics. + * + * Note: Only user interaction changes are logged. + */ +fun KeyValueStore.toPreferenceDataStore( + screen: PreferenceScreenMetadata, + preference: PreferenceMetadata, +): PreferenceDataStore { + val preferenceDataStore: PreferenceDataStore = PreferenceDataStoreAdapter(this) + val metricsLogger = + PreferenceScreenRegistry.preferenceUiActionMetricsLogger ?: return preferenceDataStore + return object : PreferenceDataStoreDelegate(preferenceDataStore) { + override fun putBoolean(key: String, value: Boolean) { + super.putBoolean(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putFloat(key: String, value: Float) { + super.putFloat(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putInt(key: String, value: Int) { + super.putInt(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putLong(key: String, value: Long) { + super.putLong(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putString(key: String, value: String?) { + super.putString(key, value) + metricsLogger.logPreferenceValueChange(screen, preference, value) + } + + override fun putStringSet(key: String, values: Set<String>?) { + super.putStringSet(key, values) + metricsLogger.logPreferenceValueChange(screen, preference, values) + } + } +} diff --git a/packages/SettingsLib/Preference/testutils/Android.bp b/packages/SettingsLib/Preference/testutils/Android.bp index 5287d6ce93f1..68b724280ac1 100644 --- a/packages/SettingsLib/Preference/testutils/Android.bp +++ b/packages/SettingsLib/Preference/testutils/Android.bp @@ -27,6 +27,7 @@ android_library { "androidx.test.core", "androidx.test.ext.junit", "flag-junit", + "mockito-kotlin2", "truth", ], } diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt index 220614bf064f..172c68a4bd37 100644 --- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt +++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt @@ -22,6 +22,8 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadata +import org.mockito.kotlin.mock /** Creates [Preference] widget and binds with metadata. */ @Suppress("UNCHECKED_CAST") @@ -29,13 +31,13 @@ import com.android.settingslib.metadata.PreferenceMetadata fun <P : Preference> PreferenceMetadata.createAndBindWidget( context: Context, preferenceScreen: PreferenceScreen? = null, + preferenceScreenMetadata: PreferenceScreenMetadata = mock(), ): P { val binding = PreferenceBindingFactory.defaultFactory.getPreferenceBinding(this)!! return (binding.createWidget(context) as P).also { if (this is PersistentPreference<*>) { - storage(context).let { keyValueStore -> - it.preferenceDataStore = PreferenceDataStoreAdapter(keyValueStore) - } + it.preferenceDataStore = + storage(context).toPreferenceDataStore(preferenceScreenMetadata, this) // Attach preference to preference screen, otherwise `Preference.performClick` does not // interact with underlying datastore (preferenceScreen ?: PreferenceScreenFactory(context).getOrCreatePreferenceScreen()) diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml index 2776544e2948..7d7bec14ed78 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml @@ -48,6 +48,7 @@ android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@android:id/title" app:layout_constraintStart_toStartOf="parent" + android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6" android:textAlignment="viewStart" android:clickable="true" android:visibility="gone" diff --git a/packages/SettingsLib/ZeroStatePreference/Android.bp b/packages/SettingsLib/ZeroStatePreference/Android.bp index 4fc00bdbfee0..0949e2c75c55 100644 --- a/packages/SettingsLib/ZeroStatePreference/Android.bp +++ b/packages/SettingsLib/ZeroStatePreference/Android.bp @@ -29,5 +29,6 @@ android_library { min_sdk_version: "28", apex_available: [ "//apex_available:platform", + "com.android.healthfitness", ], } diff --git a/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml b/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml index c0b195cc1f74..ae3f1dde8a3a 100644 --- a/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml +++ b/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml @@ -17,7 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:orientation="vertical"> diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt new file mode 100644 index 000000000000..a64e8cc07b15 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2025 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:Suppress("ktlint:standard:filename") // remove once we have more bindings + +package com.android.settingslib + +import android.content.Context +import androidx.preference.Preference +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.preference.PreferenceBinding + +/** Preference binding for [PrimarySwitchPreference]. */ +interface PrimarySwitchPreferenceBinding : PreferenceBinding { + + override fun createWidget(context: Context): Preference = PrimarySwitchPreference(context) + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + (preference as PrimarySwitchPreference).apply { + isChecked = preferenceDataStore!!.getBoolean(key, false) + isSwitchEnabled = isEnabled + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index 5bcdcc09206b..d9e79fa7c9dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -52,6 +52,14 @@ import java.util.concurrent.Executors; * BluetoothLeBroadcastAssistant.Callback} to get the result callback. */ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile { + /** A derived source state based on {@link BluetoothLeBroadcastReceiveState}. */ + public enum LocalBluetoothLeBroadcastSourceState { + UNKNOWN, + STREAMING, + DECRYPTION_FAILED, + PAUSED, + } + private static final String TAG = "LocalBluetoothLeBroadcastAssistant"; private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; private static final boolean DEBUG = BluetoothUtils.D; @@ -59,6 +67,13 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile static final String NAME = "LE_AUDIO_BROADCAST_ASSISTANT"; // Order of this profile in device profiles list private static final int ORDINAL = 1; + // Referring to Broadcast Audio Scan Service 1.0 + // Table 3.9: Broadcast Receive State characteristic format + // 0x00000000: 0b0 = Not synchronized to BIS_index[x] + // 0xFFFFFFFF: Failed to sync to BIG + private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L; + private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL; + private static final String EMPTY_DEVICE_ADDRESS = "00:00:00:00:00:00"; private LocalBluetoothProfileManager mProfileManager; private BluetoothLeBroadcastAssistant mService; @@ -558,4 +573,30 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile } } } + + /** Checks the source connection status based on the provided broadcast receive state. */ + public static LocalBluetoothLeBroadcastSourceState getLocalSourceState( + BluetoothLeBroadcastReceiveState state) { + // Source is actively streaming + if (state.getBisSyncState().stream() + .anyMatch( + bitmap -> + (bitmap != BIS_SYNC_NOT_SYNC_TO_BIS + && bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG))) { + return LocalBluetoothLeBroadcastSourceState.STREAMING; + } + // Wrong password is used for the source + if (state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED + && state.getBigEncryptionState() + == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) { + return LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; + } + // Source in hysteresis mode + if (!state.getSourceDevice().getAddress().equals(EMPTY_DEVICE_ADDRESS)) { + // Referring to Broadcast Audio Scan Service 1.0 + // All zero address means no source on the sink device + return LocalBluetoothLeBroadcastSourceState.PAUSED; + } + return LocalBluetoothLeBroadcastSourceState.UNKNOWN; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java index 98c3edba8223..c5fa0aacbda1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java @@ -44,7 +44,12 @@ import com.android.settingslib.R; import java.util.Arrays; -public class ZenDurationDialog { +/** + * This dialog configures the default behavior that the user prefers when enabling DND. + * Not to be confused with {@link EnableDndDialogFactory}, which is the dialog that will be shown + * when the user enables DND if the "Ask every time" option was selected in this dialog. + */ +public class DndDurationDialogFactory { private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; @VisibleForTesting protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0]; @@ -72,7 +77,7 @@ public class ZenDurationDialog { @VisibleForTesting protected LayoutInflater mLayoutInflater; - public ZenDurationDialog(Context context) { + public DndDurationDialogFactory(Context context) { mContext = context; } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java index c48694cecb4c..f0e7fb851d5f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java @@ -54,8 +54,15 @@ import java.util.GregorianCalendar; import java.util.Locale; import java.util.Objects; -public class EnableZenModeDialog { - private static final String TAG = "EnableZenModeDialog"; +/** + * When enabling DND, if the user has the setting to "Ask every time" for the duration, we show + * this dialog to allow the user to select for how long they want DND to be enabled this time. + * Not to be confused with {@link DndDurationDialogFactory}, which is the dialog that allows the + * user to configure the default behavior for enabling DND (and in turn may lead to this dialog + * being shown, since it contains the said "Ask every time" option). + */ +public class EnableDndDialogFactory { + private static final String TAG = "EnableDndDialogFactory"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; @@ -74,7 +81,7 @@ public class EnableZenModeDialog { private static final int MINUTES_MS = 60 * SECONDS_MS; @Nullable - private final ZenModeDialogMetricsLogger mMetricsLogger; + private final EnableDndDialogMetricsLogger mMetricsLogger; @VisibleForTesting protected Uri mForeverId; @@ -101,17 +108,17 @@ public class EnableZenModeDialog { @VisibleForTesting protected LayoutInflater mLayoutInflater; - public EnableZenModeDialog(Context context) { + public EnableDndDialogFactory(Context context) { this(context, 0); } - public EnableZenModeDialog(Context context, int themeResId) { + public EnableDndDialogFactory(Context context, int themeResId) { this(context, themeResId, false /* cancelIsNeutral */, - new ZenModeDialogMetricsLogger(context)); + new EnableDndDialogMetricsLogger(context)); } - public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral, - ZenModeDialogMetricsLogger metricsLogger) { + public EnableDndDialogFactory(Context context, int themeResId, boolean cancelIsNeutral, + EnableDndDialogMetricsLogger metricsLogger) { mContext = context; mThemeResId = themeResId; mCancelIsNeutral = cancelIsNeutral; diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java index 17695e396bef..552bf8d95dcf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java @@ -22,12 +22,12 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; /** - * Logs ui events for {@link EnableZenModeDialog}. + * Logs ui events for {@link EnableDndDialogFactory}. */ -public class ZenModeDialogMetricsLogger { +public class EnableDndDialogMetricsLogger { private final Context mContext; - public ZenModeDialogMetricsLogger(Context context) { + public EnableDndDialogMetricsLogger(Context context) { mContext = context; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java index 19845a04ea18..0b05a4f2dff0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java @@ -16,6 +16,12 @@ package com.android.settingslib.notification.modes; +import static com.android.settingslib.notification.modes.DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX; +import static com.android.settingslib.notification.modes.DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX; +import static com.android.settingslib.notification.modes.DndDurationDialogFactory.FOREVER_CONDITION_INDEX; +import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MAX_BUCKET_MINUTES; +import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MIN_BUCKET_MINUTES; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -32,6 +38,8 @@ import android.view.View; import androidx.appcompat.app.AlertDialog; +import com.android.settingslib.notification.modes.DndDurationDialogFactory.ConditionTag; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,8 +47,8 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) -public class ZenDurationDialogTest { - private ZenDurationDialog mController; +public class DndDurationDialogFactoryTest { + private DndDurationDialogFactory mController; private Context mContext; private LayoutInflater mLayoutInflater; @@ -53,7 +61,7 @@ public class ZenDurationDialogTest { mContentResolver = RuntimeEnvironment.application.getContentResolver(); mLayoutInflater = LayoutInflater.from(mContext); - mController = spy(new ZenDurationDialog(mContext)); + mController = spy(new DndDurationDialogFactory(mContext)); mController.mLayoutInflater = mLayoutInflater; mController.getContentView(); mBuilder = new AlertDialog.Builder(mContext); @@ -65,12 +73,9 @@ public class ZenDurationDialogTest { Settings.Global.ZEN_DURATION_PROMPT); mController.setupDialog(mBuilder); - assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertTrue(mController.getConditionTagAt( - ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertTrue(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); } @Test @@ -79,12 +84,9 @@ public class ZenDurationDialogTest { Settings.Secure.ZEN_DURATION_FOREVER); mController.setupDialog(mBuilder); - assertTrue(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt( - ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); + assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); } @Test @@ -92,12 +94,9 @@ public class ZenDurationDialogTest { Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION, 45); mController.setupDialog(mBuilder); - assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertTrue(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt( - ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked()); } @Test @@ -106,8 +105,7 @@ public class ZenDurationDialogTest { Settings.Secure.ZEN_DURATION_FOREVER); mController.setupDialog(mBuilder); - mController.getConditionTagAt(ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.setChecked( - true); + mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(true); mController.updateZenDuration(Settings.Secure.ZEN_DURATION_FOREVER); assertEquals(Settings.Secure.ZEN_DURATION_PROMPT, Settings.Secure.getInt(mContentResolver, @@ -120,8 +118,7 @@ public class ZenDurationDialogTest { Settings.Secure.ZEN_DURATION_PROMPT); mController.setupDialog(mBuilder); - mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb.setChecked( - true); + mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT); assertEquals(Settings.Secure.ZEN_DURATION_FOREVER, Settings.Secure.getInt(mContentResolver, @@ -134,8 +131,7 @@ public class ZenDurationDialogTest { Settings.Secure.ZEN_DURATION_PROMPT); mController.setupDialog(mBuilder); - mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb.setChecked( - true); + mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true); mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT); // countdown defaults to 60 minutes: @@ -152,59 +148,50 @@ public class ZenDurationDialogTest { // click time button starts at 60 minutes // - 1 hour to MAX_BUCKET_MINUTES (12 hours), increments by 1 hour // - 0-60 minutes increments by 15 minutes - View view = mController.mZenRadioGroupContent.getChildAt( - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); - ZenDurationDialog.ConditionTag tag = mController.getConditionTagAt( - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + View view = mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX); + ConditionTag tag = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX); // test incrementing up: - mController.onClickTimeButton(view, tag, true, ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX); assertEquals(120, tag.countdownZenDuration); // goes from 1 hour to 2 hours // try clicking up 50 times - should max out at ZenDurationDialog.MAX_BUCKET_MINUTES for (int i = 0; i < 50; i++) { - mController.onClickTimeButton(view, tag, true, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX); } - assertEquals(ZenDurationDialog.MAX_BUCKET_MINUTES, tag.countdownZenDuration); + assertEquals(MAX_BUCKET_MINUTES, tag.countdownZenDuration); // reset, test incrementing down: mController.mBucketIndex = -1; // reset current bucket index to reset countdownZenDuration tag.countdownZenDuration = 60; // back to default - mController.onClickTimeButton(view, tag, false, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX); assertEquals(45, tag.countdownZenDuration); // goes from 60 minutes to 45 minutes // try clicking down 50 times - should stop at MIN_BUCKET_MINUTES for (int i = 0; i < 50; i++) { - mController.onClickTimeButton(view, tag, false, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX); } - assertEquals(ZenDurationDialog.MIN_BUCKET_MINUTES, tag.countdownZenDuration); + assertEquals(MIN_BUCKET_MINUTES, tag.countdownZenDuration); // reset countdownZenDuration to unbucketed number, should round change to nearest bucket mController.mBucketIndex = -1; tag.countdownZenDuration = 50; - mController.onClickTimeButton(view, tag, false, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX); assertEquals(45, tag.countdownZenDuration); mController.mBucketIndex = -1; tag.countdownZenDuration = 50; - mController.onClickTimeButton(view, tag, true, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX); assertEquals(60, tag.countdownZenDuration); mController.mBucketIndex = -1; tag.countdownZenDuration = 75; - mController.onClickTimeButton(view, tag, false, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX); assertEquals(60, tag.countdownZenDuration); mController.mBucketIndex = -1; tag.countdownZenDuration = 75; - mController.onClickTimeButton(view, tag, true, - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); + mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX); assertEquals(120, tag.countdownZenDuration); } @@ -213,12 +200,9 @@ public class ZenDurationDialogTest { Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION, Settings.Secure.ZEN_DURATION_FOREVER); mController.setupDialog(mBuilder); - ZenDurationDialog.ConditionTag forever = mController.getConditionTagAt( - ZenDurationDialog.FOREVER_CONDITION_INDEX); - ZenDurationDialog.ConditionTag countdown = mController.getConditionTagAt( - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); - ZenDurationDialog.ConditionTag alwaysAsk = mController.getConditionTagAt( - ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX); + ConditionTag forever = mController.getConditionTagAt(FOREVER_CONDITION_INDEX); + ConditionTag countdown = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX); + ConditionTag alwaysAsk = mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX); forever.rb.setChecked(true); assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected"); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java index e397f97e0277..fc9fd8077fd0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java @@ -16,6 +16,10 @@ package com.android.settingslib.notification.modes; +import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_ALARM_CONDITION_INDEX; +import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_CONDITION_INDEX; +import static com.android.settingslib.notification.modes.EnableDndDialogFactory.FOREVER_CONDITION_INDEX; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -39,6 +43,8 @@ import android.net.Uri; import android.service.notification.Condition; import android.view.LayoutInflater; +import com.android.settingslib.notification.modes.EnableDndDialogFactory.ConditionTag; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,8 +54,8 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) -public class EnableZenModeDialogTest { - private EnableZenModeDialog mController; +public class EnableDndDialogFactoryTest { + private EnableDndDialogFactory mController; @Mock private Context mContext; @@ -74,7 +80,7 @@ public class EnableZenModeDialogTest { when(mFragment.getContext()).thenReturn(mShadowContext); mLayoutInflater = LayoutInflater.from(mShadowContext); - mController = spy(new EnableZenModeDialog(mContext)); + mController = spy(new EnableDndDialogFactory(mContext)); mController.mContext = mContext; mController.mLayoutInflater = mLayoutInflater; mController.mForeverId = Condition.newId(mContext).appendPath("forever").build(); @@ -101,36 +107,29 @@ public class EnableZenModeDialogTest { Uri countdown = Condition.newId(mContext).appendPath("countdown").build(); mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0); mController.bind(mCountdownCondition, - mController.mZenRadioGroupContent.getChildAt( - EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX), - EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX); + mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), + COUNTDOWN_CONDITION_INDEX); mController.bind(mAlarmCondition, mController.mZenRadioGroupContent.getChildAt( - EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX), - EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX); + COUNTDOWN_ALARM_CONDITION_INDEX), + COUNTDOWN_ALARM_CONDITION_INDEX); } @Test public void testForeverChecked() { mController.bindConditions(mController.forever()); - assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt( - EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); } @Test public void testNoneChecked() { mController.bindConditions(null); - assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt( - EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); } @Test @@ -139,12 +138,9 @@ public class EnableZenModeDialogTest { doReturn(true).when(mController).isAlarm(mAlarmCondition); mController.bindConditions(mAlarmCondition); - assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertTrue(mController.getConditionTagAt( - EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertTrue(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); } @Test @@ -153,12 +149,9 @@ public class EnableZenModeDialogTest { doReturn(true).when(mController).isCountdown(mCountdownCondition); mController.bindConditions(mCountdownCondition); - assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb - .isChecked()); - assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb - .isChecked()); - assertFalse(mController.getConditionTagAt( - EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked()); + assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked()); + assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); } @Test @@ -198,12 +191,12 @@ public class EnableZenModeDialogTest { @Test public void testAccessibility() { mController.bindConditions(null); - EnableZenModeDialog.ConditionTag forever = mController.getConditionTagAt( - ZenDurationDialog.FOREVER_CONDITION_INDEX); - EnableZenModeDialog.ConditionTag countdown = mController.getConditionTagAt( - ZenDurationDialog.COUNTDOWN_CONDITION_INDEX); - EnableZenModeDialog.ConditionTag alwaysAsk = mController.getConditionTagAt( - ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX); + ConditionTag forever = mController.getConditionTagAt( + DndDurationDialogFactory.FOREVER_CONDITION_INDEX); + ConditionTag countdown = mController.getConditionTagAt( + DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX); + ConditionTag alwaysAsk = mController.getConditionTagAt( + DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX); forever.rb.setChecked(true); assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected"); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 7b4a2ca5de39..d367748d7dbf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -92,6 +92,7 @@ public class SecureSettings { Settings.Secure.KEY_REPEAT_DELAY_MS, Settings.Secure.CAMERA_GESTURE_DISABLED, Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, + Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index b0309a8fa5a5..242bdce0d79e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -142,6 +142,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put( + Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR); @@ -454,5 +456,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, new InclusiveIntegerRangeValidator(0, 1)); VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 1c6d6816e9b4..95059779ce3d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1718,6 +1718,9 @@ class SettingsProtoDumpUtil { Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, SecureSettingsProto.Accessibility.AUTOCLICK_CURSOR_AREA_SIZE); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, + SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT); + dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED); dumpSetting(s, p, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index cbdb36fff98c..9aad5d5f8367 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -689,6 +689,7 @@ public class SettingsBackupTest { Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, Settings.Secure.DEVICE_PAIRED, Settings.Secure.DIALER_DEFAULT_APPLICATION, + Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, Settings.Secure.DISABLED_PRINT_SERVICES, Settings.Secure.DISABLE_SECURE_WINDOWS, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 4448000324d8..a044738d2e91 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -998,6 +998,11 @@ <!-- Permission required for CTS test - CtsContentProviderMultiUserTest --> <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" /> + <!-- Permissions required for CTS test - MediaQualityTest --> + <uses-permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE" /> + <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" /> + <uses-permission android:name="android.permission.READ_COLOR_ZONES" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a935aacacf95..848ea0f077ba 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -531,6 +531,7 @@ android_library { "-Adagger.fastInit=enabled", "-Adagger.explicitBindingConflictsWithInject=ERROR", "-Adagger.strictMultibindingValidation=enabled", + "-Adagger.useBindingGraphFix=ENABLED", "-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas", ], kotlincflags: ["-Xjvm-default=all"], @@ -757,6 +758,10 @@ android_library { // TODO(b/352363800): Why do we need this? "-J-Xmx8192M", ], + javacflags: [ + "-Adagger.useBindingGraphFix=ENABLED", + ], + aaptflags: [ "--extra-packages", "com.android.systemui", @@ -847,7 +852,6 @@ android_robolectric_test { "androidx.test.ext.truth", ], - instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], plugins: [ @@ -884,7 +888,6 @@ android_robolectric_test { "androidx.test.ext.truth", ], - instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], plugins: [ diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 07262533b81a..33e9919f06eb 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -40,6 +40,7 @@ florenceyang@google.com gallmann@google.com graciecheng@google.com gwasserman@google.com +helencheuk@google.com hwwang@google.com hyunyoungs@google.com ikateryna@google.com diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2d68ab8ff451..df70b0337213 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -176,13 +176,6 @@ flag { } flag { - name: "notifications_dismiss_pruned_summaries" - namespace: "systemui" - description: "NotifCollection.dismissNotifications will now dismiss summaries that are pruned from the shade." - bug: "355967751" -} - -flag { name: "notification_transparent_header_fix" namespace: "systemui" description: "fix the transparent group header issue for async header inflation." @@ -1930,3 +1923,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "magnetic_notification_horizontal_swipe" + namespace: "systemui" + description: "Add support for magnetic behavior on horizontal notification swipes." + bug: "390179908" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index 8a57e8cbbb20..f36f0306d82b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -27,6 +27,7 @@ import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRAN import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; import static com.android.wm.shell.shared.TransitionUtil.isClosingMode; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; @@ -270,7 +271,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner // skip changes that we didn't wrap if (!leashMap.containsKey(change.getLeash())) continue; // Only make the update if we are closing Desktop tasks. - if (change.getTaskInfo() != null && change.getTaskInfo().isFreeform() + if (change.getTaskInfo() != null && (change.getTaskInfo().isFreeform() + || change.hasFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY)) && isClosingMode(change.getMode())) { startTransaction.setAlpha(leashMap.get(launcherChange.getLeash()), 0f); return; diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 439968590dad..d43b596b32f1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -87,10 +87,10 @@ import androidx.compose.ui.unit.times import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformButton import com.android.compose.animation.Easings +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.windowsizeclass.LocalWindowSizeClass @@ -515,7 +515,7 @@ private fun FoldAware( } @Composable -private fun SceneScope.FoldableScene( +private fun ContentScope.FoldableScene( aboveFold: @Composable BoxScope.() -> Unit, belowFold: @Composable BoxScope.() -> Unit, isSplit: Boolean, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 55b42931b1fa..fad8ae7e3ba2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -24,8 +24,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.bouncer.ui.BouncerDialogFactory @@ -73,7 +73,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) = + override fun ContentScope.Content(modifier: Modifier) = BouncerScene( viewModel = rememberViewModel("BouncerScene") { contentViewModelFactory.create() }, dialogFactory = dialogFactory, @@ -82,7 +82,7 @@ constructor( } @Composable -private fun SceneScope.BouncerScene( +private fun ContentScope.BouncerScene( viewModel: BouncerSceneContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, @@ -96,8 +96,8 @@ private fun SceneScope.BouncerScene( drawRect(color = backgroundColor) } - // Separate the bouncer content into a reusable composable that doesn't have any SceneScope - // dependencies + // Separate the bouncer content into a reusable composable that doesn't have any + // ContentScope dependencies BouncerContent( viewModel, dialogFactory, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index a2a91fcd5d52..9b5ff7f9e7e8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -33,13 +33,13 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.observableTransitionState @@ -229,7 +229,7 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable -fun SceneScope.CommunalScene( +fun ContentScope.CommunalScene( backgroundType: CommunalBackgroundType, colors: CommunalColors, content: CommunalContent, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 0a0003ee9a8a..fea34921b853 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection @@ -65,7 +65,7 @@ constructor( ) { @Composable - fun SceneScope.Content(modifier: Modifier = Modifier) { + fun ContentScope.Content(modifier: Modifier = Modifier) { CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) { Layout( modifier = Modifier.fillMaxSize(), @@ -81,7 +81,7 @@ constructor( dialogFactory = dialogFactory, widgetSection = widgetSection, modifier = Modifier.element(Communal.Elements.Grid), - sceneScope = this@Content, + contentScope = this@Content, ) } if (communalSettingsInteractor.isV2FlagEnabled()) { @@ -193,6 +193,7 @@ constructor( companion object { private val screensaverButtonSize: Dp = 64.dp private val screensaverButtonPadding: Dp = 24.dp + // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area // position are sorted. private val lockIconSize: Dp = 54.dp 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 068df8e31ed0..3c0480d150e0 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 @@ -171,7 +171,7 @@ import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.animation.Easings.Emphasized -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.internal.R.dimen.system_app_widget_background_radius @@ -217,7 +217,7 @@ fun CommunalHub( widgetConfigurator: WidgetConfigurator? = null, onOpenWidgetPicker: (() -> Unit)? = null, onEditDone: (() -> Unit)? = null, - sceneScope: SceneScope? = null, + contentScope: ContentScope? = null, ) { val communalContent by viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) @@ -437,7 +437,7 @@ fun CommunalHub( widgetConfigurator = widgetConfigurator, interactionHandler = interactionHandler, widgetSection = widgetSection, - sceneScope = sceneScope, + contentScope = contentScope, ) } } @@ -827,7 +827,7 @@ private fun BoxScope.CommunalHubLazyGrid( widgetConfigurator: WidgetConfigurator?, interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, - sceneScope: SceneScope?, + contentScope: ContentScope?, ) { var gridModifier = Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) } @@ -1009,7 +1009,7 @@ private fun BoxScope.CommunalHubLazyGrid( interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, - sceneScope = sceneScope, + contentScope = contentScope, ) } } @@ -1065,8 +1065,7 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal ) { Icon( imageVector = Icons.Default.Add, - contentDescription = - stringResource(R.string.label_for_button_in_empty_state_cta), + contentDescription = null, modifier = Modifier.size(24.dp), ) Spacer(Modifier.width(ButtonDefaults.IconSpacing)) @@ -1261,7 +1260,7 @@ private fun CommunalContent( interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, resizeableItemFrameViewModel: ResizeableItemFrameViewModel, - sceneScope: SceneScope? = null, + contentScope: ContentScope? = null, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> @@ -1285,7 +1284,7 @@ private fun CommunalContent( is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) - is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier) + is CommunalContentModel.Umo -> Umo(viewModel, contentScope, modifier) is CommunalContentModel.Spacer -> Box(Modifier.fillMaxSize()) } } @@ -1451,7 +1450,6 @@ private fun WidgetContent( } else { Modifier } - Box( modifier = modifier @@ -1539,7 +1537,10 @@ private fun WidgetContent( with(widgetSection) { Widget( isFocusable = isFocusable, - openWidgetEditor = { viewModel.onOpenWidgetEditor() }, + openWidgetEditor = { + viewModel.setSelectedKey(model.key) + viewModel.onOpenWidgetEditor() + }, model = model, size = size, modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode), @@ -1701,11 +1702,11 @@ private fun TutorialContent(modifier: Modifier = Modifier) { @Composable private fun Umo( viewModel: BaseCommunalViewModel, - sceneScope: SceneScope?, + contentScope: ContentScope?, modifier: Modifier = Modifier, ) { - if (SceneContainerFlag.isEnabled && sceneScope != null) { - sceneScope.MediaCarousel( + if (SceneContainerFlag.isEnabled && contentScope != null) { + contentScope.MediaCarousel( modifier = modifier.fillMaxSize(), isVisible = true, mediaHost = viewModel.mediaHost, @@ -1788,6 +1789,7 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl CustomAccessibilityAction( context.getString(R.string.accessibility_action_label_edit_widgets) ) { + viewModel.setSelectedKey(null) viewModel.onOpenWidgetEditor() true }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 88b651019c4a..143fbe4de550 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.communal.shared.model.CommunalBackgroundType @@ -55,7 +55,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { val backgroundType by contentViewModel.communalBackground.collectAsStateWithLifecycle( initialValue = CommunalBackgroundType.ANIMATED diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt index 3b335fa3141e..1b0ddcb13ee2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/AmbientStatusBarSection.kt @@ -22,7 +22,7 @@ import android.widget.FrameLayout import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.ambient.statusbar.dagger.AmbientStatusBarComponent import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView import com.android.systemui.communal.ui.compose.Communal @@ -31,11 +31,9 @@ import javax.inject.Inject class AmbientStatusBarSection @Inject -constructor( - private val factory: AmbientStatusBarComponent.Factory, -) { +constructor(private val factory: AmbientStatusBarComponent.Factory) { @Composable - fun SceneScope.AmbientStatusBar(modifier: Modifier = Modifier) { + fun ContentScope.AmbientStatusBar(modifier: Modifier = Modifier) { AndroidView( factory = { context -> (LayoutInflater.from(context) @@ -49,7 +47,7 @@ constructor( factory.create(this).getController().apply { init() } } }, - modifier = modifier.element(Communal.Elements.StatusBar) + modifier = modifier.element(Communal.Elements.StatusBar), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt index f4374c6c9487..6cd0c5dfd15c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/dream/ui/composable/DreamScene.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton @@ -54,7 +54,7 @@ constructor(private val actionsViewModelFactory: DreamUserActionsViewModel.Facto } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { Box(modifier = modifier.fillMaxSize()) { // Render a sleep emoji to make the scene appear visible. Text( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index 5c5514aec03e..7b2f9dc76158 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint @@ -50,7 +50,7 @@ class LockscreenContent( } @Composable - fun SceneScope.Content(modifier: Modifier = Modifier) { + fun ContentScope.Content(modifier: Modifier = Modifier) { val viewModel = rememberViewModel("LockscreenContent-viewModel") { viewModelFactory.create() } val notificationLockscreenScrimViewModel = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index c7c29f9fdb7c..5e61af634bbc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentFloatAsState @@ -54,18 +54,13 @@ constructor( } @Composable - override fun SceneScope.Content( - modifier: Modifier, - ) { - LockscreenScene( - lockscreenContent = lockscreenContent, - modifier = modifier, - ) + override fun ContentScope.Content(modifier: Modifier) { + LockscreenScene(lockscreenContent = lockscreenContent, modifier = modifier) } } @Composable -private fun SceneScope.LockscreenScene( +private fun ContentScope.LockscreenScene( lockscreenContent: Lazy<LockscreenContent>, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt index adad4468b751..c365ec590a5f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import dagger.Binds @@ -37,14 +37,8 @@ class CommunalBlueprint @Inject constructor() : ComposableLockscreenSceneBluepri override val id: String = "communal" @Composable - override fun SceneScope.Content( - viewModel: LockscreenContentViewModel, - modifier: Modifier, - ) { - LockscreenLongPress( - viewModel = viewModel.touchHandling, - modifier = modifier, - ) { _ -> + override fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { + LockscreenLongPress(viewModel = viewModel.touchHandling, modifier = modifier) { _ -> Box(modifier.background(Color.Black)) { Text( text = "TODO(b/316211368): communal blueprint", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt index df36d0774f11..cfafb6214c45 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt @@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.composable.blueprint import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel /** Defines interface for classes that can render the content for a specific blueprint/layout. */ interface ComposableLockscreenSceneBlueprint : LockscreenSceneBlueprint { /** Renders the content of this blueprint. */ - @Composable - fun SceneScope.Content( - viewModel: LockscreenContentViewModel, - modifier: Modifier, - ) + @Composable fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 9643f192e066..c55a3fdfc6c0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.ui.composable.LockscreenLongPress @@ -68,7 +68,7 @@ constructor( override val id: String = "default" @Composable - override fun SceneScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { + override fun ContentScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle() val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt index af9a195c4575..99a7633e3cd3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt @@ -18,9 +18,9 @@ package com.android.systemui.keyguard.ui.composable.section import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope /** Defines interface for classes that can render the ambient indication area. */ interface AmbientIndicationSection { - @Composable fun SceneScope.AmbientIndication(modifier: Modifier) + @Composable fun ContentScope.AmbientIndication(modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt index 5e9ade163ac2..52ccab3b4d1e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -30,8 +30,8 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.DpSize import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.res.ResourcesCompat +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -61,7 +61,7 @@ constructor( * shortcut is placed along the edges of the display. */ @Composable - fun SceneScope.Shortcut( + fun ContentScope.Shortcut( isStart: Boolean, applyPadding: Boolean, modifier: Modifier = Modifier, @@ -89,7 +89,7 @@ constructor( } @Composable - fun SceneScope.IndicationArea(modifier: Modifier = Modifier) { + fun ContentScope.IndicationArea(modifier: Modifier = Modifier) { Element(key = IndicationAreaElementKey, modifier = modifier.indicationAreaPadding()) { content { IndicationArea( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index fb01e7039edd..34c0bcaca997 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.contains import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.customization.R import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey @@ -54,7 +54,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.SmallClock( + fun ContentScope.SmallClock( burnInParams: BurnInParameters, onTopChanged: (top: Float?) -> Unit, modifier: Modifier = Modifier, @@ -87,7 +87,7 @@ constructor( } @Composable - fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { + fun ContentScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() if (currentClock?.largeClock?.view == null) { return diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 597cbf24729b..4795e7cef87c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope import com.android.systemui.biometrics.AuthController import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application @@ -66,7 +66,7 @@ constructor( @LongPressTouchLog private val logBuffer: LogBuffer, ) { @Composable - fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { + fun ContentScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { val context = LocalContext.current AndroidView( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt index 4a9f44b74099..0ff567bf90ad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -42,7 +42,7 @@ constructor( ) { @Composable - fun SceneScope.KeyguardMediaCarousel( + fun ContentScope.KeyguardMediaCarousel( isShadeLayoutWide: Boolean, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 0344ab8e0196..2bc392d386bf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton @@ -148,7 +148,7 @@ constructor( } @Composable - fun SceneScope.HeadsUpNotifications() { + fun ContentScope.HeadsUpNotifications() { SnoozeableHeadsUpNotificationSpace( stackScrollView = stackScrollView.get(), viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() }, @@ -160,7 +160,7 @@ constructor( * adjustment */ @Composable - fun SceneScope.Notifications( + fun ContentScope.Notifications( areNotificationsVisible: Boolean, isShadeLayoutWide: Boolean, burnInParams: BurnInParameters?, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index 1cee4d67df3b..c3ba7ab2fd19 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys @@ -57,7 +57,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.SmartSpace( + fun ContentScope.SmartSpace( burnInParams: BurnInParameters, onTopChanged: (top: Float?) -> Unit, smartSpacePaddingTop: (Resources) -> Int, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt index 0d8a47019a08..172c3f5a1135 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt @@ -26,7 +26,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.height import com.android.keyguard.dagger.KeyguardStatusBarViewComponent import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout @@ -45,9 +45,10 @@ constructor( private val notificationPanelView: Lazy<NotificationPanelView>, ) { @Composable - fun SceneScope.StatusBar(modifier: Modifier = Modifier) { + fun ContentScope.StatusBar(modifier: Modifier = Modifier) { val context = LocalContext.current val viewDisplayCutout = LocalDisplayCutout.current.viewDisplayCutoutKeyguardStatusBarView + @SuppressLint("InflateParams") val view = remember(context) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt index 73c4fab7b646..6250da379402 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt @@ -30,9 +30,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.padding import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys import com.android.systemui.keyguard.ui.composable.modifier.burnInAware @@ -50,15 +49,10 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, ) { @Composable - fun SceneScope.Time( - clock: ClockController, - burnInParams: BurnInParameters, - ) { + fun ContentScope.Time(clock: ClockController, burnInParams: BurnInParameters) { Row( modifier = - Modifier.padding( - horizontal = dimensionResource(customR.dimen.clock_padding_start) - ) + Modifier.padding(horizontal = dimensionResource(customR.dimen.clock_padding_start)) .burnInAware(aodBurnInViewModel, burnInParams, isClock = true) ) { WeatherElement( @@ -70,10 +64,7 @@ constructor( } @Composable - private fun SceneScope.Date( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.Date(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_date, clock = clock, @@ -83,10 +74,7 @@ constructor( } @Composable - private fun SceneScope.Weather( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.Weather(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_weather_icon, clock = clock, @@ -96,10 +84,7 @@ constructor( } @Composable - private fun SceneScope.DndAlarmStatus( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.DndAlarmStatus(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_alarm_dnd, clock = clock, @@ -109,10 +94,7 @@ constructor( } @Composable - private fun SceneScope.Temperature( - clock: ClockController, - modifier: Modifier = Modifier, - ) { + private fun ContentScope.Temperature(clock: ClockController, modifier: Modifier = Modifier) { WeatherElement( weatherClockElementViewId = customR.id.weather_clock_temperature, clock = clock, @@ -122,7 +104,7 @@ constructor( } @Composable - private fun SceneScope.WeatherElement( + private fun ContentScope.WeatherElement( weatherClockElementViewId: Int, clock: ClockController, elementKey: ElementKey, @@ -144,32 +126,28 @@ constructor( } }, update = {}, - modifier = modifier + modifier = modifier, ) } } } @Composable - fun SceneScope.LargeClockSectionBelowSmartspace( + fun ContentScope.LargeClockSectionBelowSmartspace( burnInParams: BurnInParameters, clock: ClockController, ) { Row( modifier = Modifier.height(IntrinsicSize.Max) - .padding( - horizontal = dimensionResource(customR.dimen.clock_padding_start) - ) + .padding(horizontal = dimensionResource(customR.dimen.clock_padding_start)) .burnInAware(aodBurnInViewModel, burnInParams, isClock = true) ) { Date(clock = clock, modifier = Modifier.wrapContentSize()) Box( modifier = Modifier.fillMaxSize() - .padding( - start = dimensionResource(customR.dimen.clock_padding_start) - ) + .padding(start = dimensionResource(customR.dimen.clock_padding_start)) ) { Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart)) Temperature(clock = clock, modifier = Modifier.align(Alignment.BottomEnd)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index b5d78398028d..f5de7dca6d9d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -31,8 +31,8 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.MovableElementKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.media.controls.ui.composable.MediaCarouselStateLoader.stateForMediaCarouselContent import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -52,7 +52,7 @@ object MediaCarousel { } @Composable -fun SceneScope.MediaCarousel( +fun ContentScope.MediaCarousel( isVisible: Boolean, mediaHost: MediaHost, modifier: Modifier = Modifier, @@ -136,6 +136,6 @@ private fun ViewGroup.setView(view: View) { } @Composable -fun SceneScope.isLandscape(): Boolean { +fun ContentScope.isLandscape(): Boolean { return LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt index bad74052b669..525284207744 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt @@ -17,8 +17,8 @@ package com.android.systemui.media.controls.ui.composable import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.content.state.TransitionState import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager @@ -50,6 +50,7 @@ object MediaCarouselStateLoader { if (isSplitShade) MediaHierarchyManager.LOCATION_QS else MediaHierarchyManager.LOCATION_QQS } + Scenes.Lockscreen -> MediaHierarchyManager.LOCATION_LOCKSCREEN Scenes.Communal -> MediaHierarchyManager.LOCATION_COMMUNAL_HUB else -> MediaHierarchyManager.LOCATION_UNKNOWN @@ -69,6 +70,7 @@ object MediaCarouselStateLoader { /** State for media carousel. */ sealed interface State { val transitionProgress: Float + // TODO b/368368388: implement media squishiness val squishFraction: () -> Float @MediaLocation val startLocation: Int @@ -100,7 +102,7 @@ object MediaCarouselStateLoader { } /** Returns the state of media carousel */ - fun SceneScope.stateForMediaCarouselContent(isInSplitShade: Boolean): State { + fun ContentScope.stateForMediaCarouselContent(isInSplitShade: Boolean): State { return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { if (MediaContentPicker.contents.contains(transitionState.currentScene)) { @@ -109,6 +111,7 @@ object MediaCarouselStateLoader { State.Gone } } + is TransitionState.Transition.ChangeScene -> with(transitionState) { if ( @@ -130,6 +133,7 @@ object MediaCarouselStateLoader { State.Gone } } + is TransitionState.Transition.OverlayTransition -> with(transitionState) { if ( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 7f7273d710a1..25b673bee1cd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -72,7 +72,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.Expandable -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.fadingBackground import com.android.compose.theme.colorAttr import com.android.systemui.Flags.notificationShadeBlur @@ -91,7 +91,7 @@ import com.android.systemui.res.R import kotlinx.coroutines.launch @Composable -fun SceneScope.FooterActionsWithAnimatedVisibility( +fun ContentScope.FooterActionsWithAnimatedVisibility( viewModel: FooterActionsViewModel, isCustomizing: Boolean, customizingAnimationDuration: Int, @@ -256,6 +256,7 @@ private fun RowScope.ForegroundServicesButton( } else { NumberButton( model.foregroundServicesCount, + contentDescription = model.text, showNewDot = model.hasNewChanges, onClick = model.onClick, ) @@ -284,6 +285,7 @@ fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifie @Composable private fun NumberButton( number: Int, + contentDescription: String, showNewDot: Boolean, onClick: (Expandable) -> Unit, modifier: Modifier = Modifier, @@ -308,14 +310,16 @@ private fun NumberButton( ) { Box(Modifier.size(40.dp)) { Box( - Modifier - .fillMaxSize() + Modifier.fillMaxSize() .clip(CircleShape) .indication(interactionSource, LocalIndication.current) ) { Text( number.toString(), - modifier = Modifier.align(Alignment.Center), + modifier = + Modifier.align(Alignment.Center).semantics { + this.contentDescription = contentDescription + }, style = MaterialTheme.typography.bodyLarge, color = colorAttr(R.attr.onShadeInactiveVariant), // TODO(b/242040009): This should only use a standard text style instead and @@ -337,9 +341,7 @@ private fun NewChangesDot(modifier: Modifier = Modifier) { val contentDescription = stringResource(R.string.fgs_dot_content_description) val color = MaterialTheme.colorScheme.tertiary - Canvas(modifier - .size(12.dp) - .semantics { this.contentDescription = contentDescription }) { + Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) { drawCircle(color) } } @@ -368,9 +370,7 @@ private fun TextButton( Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)), verticalAlignment = Alignment.CenterVertically, ) { - Icon(icon, Modifier - .padding(end = 12.dp) - .size(20.dp)) + Icon(icon, Modifier.padding(end = 12.dp).size(20.dp)) Text( text, @@ -391,9 +391,7 @@ private fun TextButton( Icon( painterResource(com.android.internal.R.drawable.ic_chevron_end), contentDescription = null, - Modifier - .padding(start = 8.dp) - .size(20.dp), + Modifier.padding(start = 8.dp).size(20.dp), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 58336c2e9d41..b826187578e0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -36,10 +36,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.MovableElementContentPicker import com.android.compose.animation.scene.MovableElementKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.content.state.TransitionState @@ -98,7 +98,7 @@ object QuickSettings { } } -private fun SceneScope.stateForQuickSettingsContent( +private fun ContentScope.stateForQuickSettingsContent( isSplitShade: Boolean, squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default }, ): QSSceneAdapter.State { @@ -141,7 +141,7 @@ private fun SceneScope.stateForQuickSettingsContent( /** * This composable will show QuickSettingsContent in the correct state (as determined by its - * [SceneScope]). + * [ContentScope]). * * If adding to scenes not in: * * QuickSettingsScene @@ -153,7 +153,7 @@ private fun SceneScope.stateForQuickSettingsContent( * * this doc. */ @Composable -fun SceneScope.QuickSettings( +fun ContentScope.QuickSettings( qsSceneAdapter: QSSceneAdapter, heightProvider: () -> Int, isSplitShade: Boolean, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 26cf7066124a..4bfbb3a908fa 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -68,7 +68,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState @@ -144,7 +144,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { QuickSettingsScene( notificationStackScrollView = notificationStackScrollView.get(), viewModelFactory = contentViewModelFactory, @@ -164,7 +164,7 @@ constructor( } @Composable -private fun SceneScope.QuickSettingsScene( +private fun ContentScope.QuickSettingsScene( notificationStackScrollView: NotificationScrollView, viewModelFactory: QuickSettingsSceneContentViewModel.Factory, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 9ee25c3404ec..2175a59e8e4d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentDpAsState @@ -71,7 +71,7 @@ constructor( } @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { val isIdleAndNotOccluded by remember { derivedStateOf { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt index 8d8ab8ee7949..6c80c69b7a34 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt @@ -18,8 +18,8 @@ package com.android.systemui.scene.ui.composable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.systemui.lifecycle.Activatable /** @@ -35,5 +35,5 @@ interface Scene : Activatable, ActionableContent { /** Uniquely-identifying key for this scene. The key must be unique within its container. */ val key: SceneKey - @Composable fun SceneScope.Content(modifier: Modifier) + @Composable fun ContentScope.Content(modifier: Modifier) } 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 bfcde7dab6d2..3131b539c6af 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 @@ -58,9 +58,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState import com.android.compose.animation.scene.content.state.TransitionState @@ -122,7 +122,7 @@ object ShadeHeader { } @Composable -fun SceneScope.CollapsedShadeHeader( +fun ContentScope.CollapsedShadeHeader( viewModelFactory: ShadeHeaderViewModel.Factory, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -264,7 +264,7 @@ fun SceneScope.CollapsedShadeHeader( } @Composable -fun SceneScope.ExpandedShadeHeader( +fun ContentScope.ExpandedShadeHeader( viewModelFactory: ShadeHeaderViewModel.Factory, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -339,7 +339,7 @@ fun SceneScope.ExpandedShadeHeader( } @Composable -private fun SceneScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) { +private fun ContentScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) { val layoutDirection = LocalLayoutDirection.current Element(key = ShadeHeader.Elements.Clock, modifier = modifier) { @@ -446,7 +446,7 @@ private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifie } @Composable -private fun SceneScope.StatusIcons( +private fun ContentScope.StatusIcons( viewModel: ShadeHeaderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, statusBarIconController: StatusBarIconController, @@ -548,7 +548,10 @@ private fun SystemIconContainer( } @Composable -private fun SceneScope.PrivacyChip(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { +private fun ContentScope.PrivacyChip( + viewModel: ShadeHeaderViewModel, + modifier: Modifier = Modifier, +) { val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle() AndroidView( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 0d3bab24f68f..f829a0d6facf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -62,9 +62,9 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentDpAsState @@ -160,7 +160,7 @@ constructor( override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions @Composable - override fun SceneScope.Content(modifier: Modifier) = + override fun ContentScope.Content(modifier: Modifier) = ShadeScene( notificationStackScrollView.get(), viewModel = @@ -193,7 +193,7 @@ constructor( } @Composable -private fun SceneScope.ShadeScene( +private fun ContentScope.ShadeScene( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneContentViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, @@ -242,7 +242,7 @@ private fun SceneScope.ShadeScene( } @Composable -private fun SceneScope.SingleShade( +private fun ContentScope.SingleShade( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneContentViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, @@ -410,7 +410,7 @@ private fun SceneScope.SingleShade( } @Composable -private fun SceneScope.SplitShade( +private fun ContentScope.SplitShade( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneContentViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, @@ -632,7 +632,7 @@ private fun SceneScope.SplitShade( } @Composable -private fun SceneScope.ShadeMediaCarousel( +private fun ContentScope.ShadeMediaCarousel( isVisible: Boolean, isInRow: Boolean, mediaHost: MediaHost, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt index 3295dde55238..bcd4d925814b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.Expandable @@ -54,7 +55,7 @@ constructor( VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN, 0, null, - viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive } + viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }, ) val gravity = horizontalGravity or Gravity.BOTTOM volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) }) @@ -95,14 +96,14 @@ constructor( icon = { Icon(icon = buttonViewModel.button.icon) }, label = { Text( - modifier = Modifier.basicMarquee(), text = label, style = MaterialTheme.typography.labelMedium, color = LocalContentColor.current, textAlign = TextAlign.Center, - maxLines = 2 + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) - } + }, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt index 25892c5a75cc..d9e8f02f005b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt @@ -19,7 +19,6 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween @@ -70,8 +69,6 @@ private const val SHRINK_FRACTION = 0.55f private const val SCALE_FRACTION = 0.9f private const val EXPAND_BUTTON_SCALE = 0.8f -/** Volume sliders laid out in a collapsable column */ -@OptIn(ExperimentalAnimationApi::class) @Composable fun ColumnVolumeSliders( viewModels: List<SliderViewModel>, @@ -144,8 +141,7 @@ fun ColumnVolumeSliders( VolumeSlider( modifier = - Modifier.padding(top = 16.dp) - .fillMaxWidth() + Modifier.fillMaxWidth() .animateEnterExit( enter = enterTransition( @@ -157,7 +153,10 @@ fun ColumnVolumeSliders( index = index, totalCount = viewModels.size, ), - ), + ) + .thenIf(!Flags.volumeRedesign()) { + Modifier.padding(top = 16.dp) + }, state = sliderState, onValueChange = { newValue: Float -> sliderViewModel.onValueChanged(sliderState, newValue) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 5f991fbb50df..bdd0da9ce4a4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -17,10 +17,12 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource @@ -33,6 +35,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon as MaterialIcon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.Text @@ -47,6 +50,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.clearAndSetSemantics @@ -67,6 +71,7 @@ import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState import kotlin.math.round import kotlinx.coroutines.flow.distinctUntilChanged @@ -98,7 +103,7 @@ fun VolumeSlider( } val value by valueState(state) - Column(modifier) { + Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth().height(40.dp), @@ -127,7 +132,7 @@ fun VolumeSlider( enabled = state.isEnabled, modifier = Modifier.height(40.dp) - .padding(vertical = 8.dp) + .padding(top = 4.dp, bottom = 12.dp) .sysuiResTag(state.label) .clearAndSetSemantics { if (state.isEnabled) { @@ -168,6 +173,28 @@ fun VolumeSlider( } }, ) + state.disabledMessage?.let { disabledMessage -> + AnimatedVisibility(visible = !state.isEnabled) { + Row( + modifier = Modifier.padding(bottom = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + MaterialIcon( + painter = painterResource(R.drawable.ic_error_outline), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(16.dp), + ) + Text( + text = disabledMessage, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.basicMarquee(), + ) + } + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt index 6349c1406a12..bc3013239289 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt @@ -37,18 +37,17 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent( layout: ComponentsLayout, modifier: Modifier = Modifier, ) { - Column( - modifier = modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(20.dp), - ) { + Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(20.dp)) { for (component in layout.headerComponents) { AnimatedVisibility(component.isVisible) { with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } } } - for (component in layout.contentComponents) { - AnimatedVisibility(component.isVisible) { - with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } + Column(Modifier.verticalScroll(rememberScrollState())) { + for (component in layout.contentComponents) { + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } + } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index ef11932867a0..a1117e1bc1db 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -264,9 +264,6 @@ interface BaseContentScope : ElementStateScope { ) } -@Deprecated("Use ContentScope instead", ReplaceWith("ContentScope")) -typealias SceneScope = ContentScope - @Stable @ElementDsl interface ContentScope : BaseContentScope { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 005146997813..b405fbe89302 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -2095,7 +2095,7 @@ class ElementTest { val foo = ElementKey("Foo", placeAllCopies = true) @Composable - fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) { + fun ContentScope.Foo(size: Dp, modifier: Modifier = Modifier) { Box(modifier.element(foo).size(size)) } @@ -2159,7 +2159,7 @@ class ElementTest { // Foo is a simple element that does not move or resize during the transition. @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { + fun ContentScope.Foo(modifier: Modifier = Modifier) { Box( modifier .element(TestElements.Foo) @@ -2211,7 +2211,7 @@ class ElementTest { @Ignore("b/363964445") fun interruption_considerPreviousUniqueState() { @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { + fun ContentScope.Foo(modifier: Modifier = Modifier) { Box(modifier.element(TestElements.Foo).size(50.dp)) } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index b4c8ad7c3327..9e1bae577ed2 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -373,7 +373,7 @@ class MovableElementTest { val fooParentInOverlayTag = "fooParentTagInOverlay" @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { + fun ContentScope.Foo(modifier: Modifier = Modifier) { // Foo wraps its content, so there is no way for STL to know its size in advance. MovableElement(foo, modifier) { content { Box(Modifier.size(fooSize)) } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt index bad4c6298e6b..93fa51654ca1 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt @@ -547,7 +547,7 @@ class OverlayTest { val sharedIntValueByContent = mutableMapOf<ContentKey, Int>() @Composable - fun SceneScope.animateContentInt(targetValue: Int) { + fun ContentScope.animateContentInt(targetValue: Int) { val animatedValue = animateContentIntAsState(targetValue, sharedIntKey) LaunchedEffect(animatedValue) { try { diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt index 4b5e9de2cce7..72304a19c17d 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -75,6 +75,7 @@ constructor( private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, + private val systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME, ) : MessageBuffer { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } @@ -244,10 +245,11 @@ constructor( } private fun echoToSystrace(level: LogLevel, tag: String, strMessage: String) { + if (!Trace.isEnabled()) return Trace.instantForTrack( Trace.TRACE_TAG_APP, - "UI Events", - "$name - ${level.shortString} $tag: $strMessage" + systraceTrackName, + "$name - ${level.shortString} $tag: $strMessage", ) } @@ -261,6 +263,10 @@ constructor( LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception) } } + + companion object { + const val DEFAULT_LOGBUFFER_TRACK_NAME = "UI Events" + } } private const val TAG = "LogBuffer" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt index a65415509d38..a6ed37ead7ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt @@ -37,10 +37,10 @@ import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.isElement @@ -270,7 +270,7 @@ class BouncerPredictiveBackTest : SysuiTestCase() { override val userActions: Flow<Map<UserAction, UserActionResult>> = flowOf() @Composable - override fun SceneScope.Content(modifier: Modifier) { + override fun ContentScope.Content(modifier: Modifier) { Box(modifier = modifier, contentAlignment = Alignment.Center) { Text(text = "Fake Lockscreen") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index 47cba0723804..030233625027 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -367,8 +367,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to DeviceEntryRestrictionReason.AdaptiveAuthRequest, - LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to - DeviceEntryRestrictionReason.BouncerLockedOut, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to DeviceEntryRestrictionReason.SecurityTimeout, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to @@ -403,8 +401,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to DeviceEntryRestrictionReason.AdaptiveAuthRequest, - LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to - DeviceEntryRestrictionReason.BouncerLockedOut, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to DeviceEntryRestrictionReason.SecurityTimeout, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to @@ -440,8 +436,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to DeviceEntryRestrictionReason.AdaptiveAuthRequest, - LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to - DeviceEntryRestrictionReason.BouncerLockedOut, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to DeviceEntryRestrictionReason.SecurityTimeout, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index bf49186a7f01..451ebf32c367 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -27,7 +27,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.notification.modes.EnableZenModeDialog +import com.android.settingslib.notification.modes.EnableDndDialogFactory import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable @@ -85,7 +85,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var zenModeController: ZenModeController @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var conditionUri: Uri - @Mock private lateinit var enableZenModeDialog: EnableZenModeDialog + @Mock private lateinit var mEnableDndDialogFactory: EnableDndDialogFactory @Captor private lateinit var spyZenMode: ArgumentCaptor<Int> @Captor private lateinit var spyConditionId: ArgumentCaptor<Uri?> @@ -105,7 +105,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { testDispatcher, testScope.backgroundScope, conditionUri, - enableZenModeDialog, + mEnableDndDialogFactory, ) } @@ -322,7 +322,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { testScope.runTest { val expandable: Expandable = mock() secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT) - whenever(enableZenModeDialog.createDialog()).thenReturn(mock()) + whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock()) collectLastValue(underTest.lockScreenState) runCurrent() @@ -344,7 +344,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { whenever(zenModeController.isZenAvailable).thenReturn(true) whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT) - whenever(enableZenModeDialog.createDialog()).thenReturn(mock()) + whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock()) collectLastValue(underTest.lockScreenState) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 605a5d261424..bafabe07d370 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -22,6 +22,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.view.IRemoteAnimationFinishedCallback +import android.view.RemoteAnimationTarget import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -39,11 +40,11 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.anyInt -import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -230,12 +231,15 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { @Test fun remoteAnimationInstantlyFinished_ifDismissTransitionNotStarted() { val mockedCallback = mock<IRemoteAnimationFinishedCallback>() - whenever(keyguardDismissTransitionInteractor.startDismissKeyguardTransition(any())) - .thenReturn(false) + + // Call the onAlreadyGone callback immediately. + doAnswer { invocation -> (invocation.getArgument(1) as (() -> Unit)).invoke() } + .whenever(keyguardDismissTransitionInteractor) + .startDismissKeyguardTransition(any(), any()) underTest.onKeyguardGoingAwayRemoteAnimationStart( transit = 0, - apps = emptyArray(), + apps = arrayOf(mock<RemoteAnimationTarget>()), wallpapers = emptyArray(), nonApps = emptyArray(), finishedCallback = mockedCallback, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt index 4936f8559bfb..d782d1e2612c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt @@ -15,16 +15,25 @@ */ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -46,17 +55,33 @@ class AodToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, - transitionFactory = { value, state -> - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.PRIMARY_BOUNCER, - value = value, - transitionState = state, - ownerName = "AodToPrimaryBouncerTransitionViewModelTest", - ) - }, + transitionFactory = ::step, actualValuesProvider = { values }, checkInterpolatedValues = false, ) } + + @Test + @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP) + fun aodToPrimaryBouncerHidesLockscreen() = + testScope.runTest { + val lockscreenAlpha by collectValues(underTest.lockscreenAlpha) + val notificationAlpha by collectValues(underTest.notificationAlpha) + + val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED)) + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope) + runCurrent() + + lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + } + + private fun step(value: Float, transitionState: TransitionState = RUNNING) = + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = transitionState, + ownerName = "AodToPrimaryBouncerTransitionViewModelTest", + ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt index 0d487509a83f..4d58f7ab118e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt @@ -16,21 +16,26 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -85,6 +90,21 @@ class DozingToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { ) } + @Test + @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP) + fun dozingToPrimaryBouncerHidesLockscreen() = + testScope.runTest { + val lockscreenAlpha by collectValues(underTest.lockscreenAlpha) + val notificationAlpha by collectValues(underTest.notificationAlpha) + + val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED)) + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope) + runCurrent() + + lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + } + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( from = KeyguardState.DOZING, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt index ec0773f79328..5a350435002f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -35,7 +34,17 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class QSColumnsRepositoryTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_dual_shade_num_columns, + 2, + ) + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_split_shade_num_columns, + 3, + ) + } private lateinit var underTest: QSColumnsRepository @Before @@ -63,7 +72,7 @@ class QSColumnsRepositoryTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.dualShadeColumns) - assertThat(latest).isEqualTo(4) + assertThat(latest).isEqualTo(2) } } @@ -72,9 +81,8 @@ class QSColumnsRepositoryTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val latest by collectLastValue(underTest.splitShadeColumns) - fakeShadeRepository.setShadeLayoutWide(true) - assertThat(latest).isEqualTo(4) + assertThat(latest).isEqualTo(3) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt index 9173ac969324..f005375a2ef9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt @@ -48,6 +48,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings @@ -123,7 +124,12 @@ class ModesTileTest : SysuiTestCase() { ) userActionInteractor = - ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor) + ModesTileUserActionInteractor( + inputHandler, + dialogDelegate, + kosmos.zenModeInteractor, + kosmos.modesDialogEventLogger, + ) underTest = ModesTile( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index a06353171c33..6a33b5f58820 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -54,7 +54,7 @@ class QSTileLoggerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer) + whenever(logBufferFactory.create(any(), any(), any(), any(), any())).thenReturn(logBuffer) val tileSpec: TileSpec = TileSpec.create("chatty_tile") underTest = QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt index c8b3aba9b846..89b8e9171076 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,7 +61,12 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { private val zenModeInteractor = kosmos.zenModeInteractor private val underTest = - ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor) + ModesTileUserActionInteractor( + inputHandler, + mockDialogDelegate, + zenModeInteractor, + kosmos.modesDialogEventLogger, + ) @Test fun handleClick_active_showsDialog() = runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index ac3089d9286b..b0af8b180cce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -30,10 +30,12 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel @@ -64,7 +66,7 @@ class CallChipViewModelTest : SysuiTestCase() { .thenReturn(chipBackgroundView) } - private val underTest = kosmos.callChipViewModel + private val underTest by lazy { kosmos.callChipViewModel } @Test fun chip_noCall_isHidden() = @@ -219,28 +221,94 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - fun chip_positiveStartTime_colorsAreThemed() = + fun chip_positiveStartTime_notPromoted_colorsAreThemed() = testScope.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 1000)) + repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null)) assertThat((latest as OngoingActivityChipModel.Shown).colors) .isEqualTo(ColorsModel.Themed) } @Test - fun chip_zeroStartTime_colorsAreThemed() = + fun chip_zeroStartTime_notPromoted_colorsAreThemed() = testScope.runTest { val latest by collectLastValue(underTest.chip) - repo.setOngoingCallState(inCallModel(startTimeMs = 0)) + repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null)) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo(ColorsModel.Themed) + } + + @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_positiveStartTime_promoted_notifChipsFlagOff_colorsAreThemed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) assertThat((latest as OngoingActivityChipModel.Shown).colors) .isEqualTo(ColorsModel.Themed) } @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreThemed() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo(ColorsModel.Themed) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_positiveStartTime_promoted_notifChipsFlagOn_colorsAreCustom() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 1000, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo( + ColorsModel.Custom( + backgroundColorInt = PROMOTED_BACKGROUND_COLOR, + primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR, + ) + ) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun chip_zeroStartTime_promoted_notifChipsFlagOff_colorsAreCustom() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel(startTimeMs = 0, promotedContent = PROMOTED_CONTENT_WITH_COLOR) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).colors) + .isEqualTo( + ColorsModel.Custom( + backgroundColorInt = PROMOTED_BACKGROUND_COLOR, + primaryTextColorInt = PROMOTED_PRIMARY_TEXT_COLOR, + ) + ) + } + + @Test fun chip_resetsCorrectly() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -275,7 +343,7 @@ class CallChipViewModelTest : SysuiTestCase() { repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null)) - assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull() + assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull() } @Test @@ -285,7 +353,7 @@ class CallChipViewModelTest : SysuiTestCase() { val pendingIntent = mock<PendingIntent>() repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent)) - val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener + val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -302,7 +370,8 @@ class CallChipViewModelTest : SysuiTestCase() { val pendingIntent = mock<PendingIntent>() repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent)) - val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener + val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy + assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -319,5 +388,19 @@ class CallChipViewModelTest : SysuiTestCase() { } else { mock<StatusBarIconView>() } + + private val PROMOTED_CONTENT_WITH_COLOR = + PromotedNotificationContentModel.Builder("notif") + .apply { + this.colors = + PromotedNotificationContentModel.Colors( + backgroundColor = PROMOTED_BACKGROUND_COLOR, + primaryTextColor = PROMOTED_PRIMARY_TEXT_COLOR, + ) + } + .build() + + private const val PROMOTED_BACKGROUND_COLOR = 65 + private const val PROMOTED_PRIMARY_TEXT_COLOR = 98 } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt index c511c433d92d..fcf8c834dc12 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel import android.content.DialogInterface +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -25,6 +26,7 @@ import com.android.internal.jank.Cuj import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.Expandable import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -46,8 +48,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.policy.CastDevice import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -84,6 +88,8 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.castToOtherDeviceChipViewModel @@ -263,7 +269,13 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockScreenCastDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden... @@ -296,7 +308,13 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockGenericCastDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden... @@ -416,13 +434,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectionStateEntireScreen_clickListenerShowsScreenCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -431,6 +450,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectionStateSingleTask_clickListenerShowsScreenCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -442,7 +462,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -451,6 +471,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_routerStateCasting_clickListenerShowsGenericCastDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -466,7 +487,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -480,13 +501,14 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectionStateCasting_clickListenerHasCuj() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() @@ -499,6 +521,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_routerStateCasting_clickListenerHasCuj() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -514,7 +537,7 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { ) ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() @@ -525,4 +548,103 @@ class CastToOtherDeviceChipViewModelTest : SysuiTestCase() { .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) assertThat(cujCaptor.firstValue.tag).contains("Cast") } + + @Test + @EnableFlags(StatusBarChipsModernization.FLAG_NAME) + fun chip_routerStateCasting_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaRouterRepo.castDevices.value = + listOf( + CastDevice( + state = CastDevice.CastState.Connected, + id = "id", + name = "name", + description = "desc", + origin = CastDevice.CastOrigin.MediaRouter, + ) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarChipsModernization.FLAG_NAME) + fun chip_projectionStateCasting_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectionStateEntireScreen_clickBehaviorShowsScreenCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenCastDialog), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectionStateSingleTask_clickBehaviorShowsScreenCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + CAST_TO_OTHER_DEVICES_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenCastDialog), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_routerStateCasting_clickBehaviorShowsGenericCastDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaRouterRepo.castDevices.value = + listOf( + CastDevice( + state = CastDevice.CastState.Connected, + id = "id", + name = "name", + description = "desc", + origin = CastDevice.CastOrigin.MediaRouter, + ) + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockGenericCastDialog), any(), anyBoolean()) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt index ee4a52d35d68..e89c929a5827 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -178,6 +179,37 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() { assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon) } + /** Regression test for b/388521980. */ + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun notificationChips_callNotifIsAlsoPromoted_callNotifExcluded() = + kosmos.runTest { + val latest by collectLastValue(underTest.notificationChips) + + setNotifs( + listOf( + activeNotificationModel( + key = "promotedNormal", + statusBarChipIcon = mock(), + promotedContent = + PromotedNotificationContentModel.Builder("promotedNormal").build(), + callType = CallType.None, + ), + activeNotificationModel( + key = "promotedCall", + statusBarChipIcon = mock(), + promotedContent = + PromotedNotificationContentModel.Builder("promotedCall").build(), + callType = CallType.Ongoing, + ), + ) + ) + + // Verify the promoted call notification is not included + assertThat(latest).hasSize(1) + assertThat(latest!![0].key).isEqualTo("promotedNormal") + } + @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun notificationChips_notifUpdatesGoThrough() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 902db5e10589..eec23d3ffb1a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -650,7 +650,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { ) val chip = latest!![0] - chip.onClickListener!!.onClick(mock<View>()) + chip.onClickListenerLegacy!!.onClick(mock<View>()) assertThat(latestChipTap).isEqualTo("clickTest") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt index 48d8add6b33a..1f82dcd9c308 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt @@ -17,12 +17,15 @@ package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel import android.content.DialogInterface +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.Expandable import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue @@ -41,8 +44,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat @@ -77,6 +82,8 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.screenRecordChipViewModel @@ -106,7 +113,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull() - assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull() + assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull() } // The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995. @@ -177,7 +184,13 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN both the screen record chip and the share-to-app chip are immediately hidden... @@ -263,13 +276,14 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_notProjecting_clickListenerShowsDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -279,6 +293,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectingEntireScreen_clickListenerShowsDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -286,7 +301,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen("host.package") - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -296,6 +311,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_projectingSingleTask_clickListenerShowsDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -307,7 +323,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { FakeActivityTaskManager.createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -317,22 +333,85 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { } @Test - fun chip_clickListenerHasCuj() = + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) + fun chip_clickListenerHasCujLegacy() = testScope.runTest { val latest by collectLastValue(underTest.chip) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen("host.package") - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() verify(kosmos.mockDialogTransitionAnimator) .showFromView(any(), any(), cujCaptor.capture(), anyBoolean()) - assertThat(cujCaptor.firstValue.cujType) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) assertThat(cujCaptor.firstValue.tag).contains("Screen record") } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_recordingState_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_notProjecting_expandActionBehaviorShowsDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectingEntireScreen_expandActionBehaviorShowsDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_projectingSingleTask_expandActionBehaviorShowsDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + "host.package", + hostDeviceName = null, + FakeActivityTaskManager.createTask(taskId = 1), + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean()) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt index b3dec2eaa1c6..36fc5aa16407 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel import android.content.DialogInterface +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -25,6 +26,7 @@ import com.android.internal.jank.Cuj import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.Expandable import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -45,8 +47,10 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -81,6 +85,8 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.shareToAppChipViewModel @@ -215,7 +221,13 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { // WHEN the stop action on the dialog is clicked val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockScreenShareDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockScreenShareDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden... @@ -268,13 +280,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP) + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_noScreen_clickListenerShowsGenericShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -288,13 +301,14 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_entireScreen_clickListenerShowsScreenShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -308,6 +322,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_singleTask_clickListenerShowsScreenShareDialog() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -318,7 +333,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) assertThat(clickListener).isNotNull() clickListener!!.onClick(chipView) @@ -332,6 +347,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun chip_clickListenerHasCuj() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -342,7 +358,7 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) + val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) clickListener!!.onClick(chipView) val cujCaptor = argumentCaptor<DialogCuj>() @@ -353,4 +369,101 @@ class ShareToAppChipViewModelTest : SysuiTestCase() { .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) assertThat(cujCaptor.firstValue.tag).contains("Share") } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_noScreen_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_entireScreen_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_singleTask_hasClickBehavior() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + NORMAL_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior) + .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java) + } + + @Test + @EnableFlags( + FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP, + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME, + ) + fun chip_noScreen_clickBehaviorShowsGenericShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockGenericShareDialog), any(), any()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_entireScreen_clickBehaviorShowsScreenShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenShareDialog), any(), any()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun chip_singleTask_clickBehaviorShowsScreenShareDialog() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + NORMAL_PACKAGE, + hostDeviceName = null, + createTask(taskId = 1), + ) + + val expandAction = + ((latest as OngoingActivityChipModel.Shown).clickBehavior + as OngoingActivityChipModel.ClickBehavior.ExpandAction) + expandAction.onClick(mockExpandable) + + verify(kosmos.mockDialogTransitionAnimator) + .show(eq(mockScreenShareDialog), any(), any()) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt index 8d4c68de8c79..d099e70c9bc5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt @@ -57,7 +57,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { icon = createIcon(R.drawable.ic_cake), colors = ColorsModel.Themed, startTimeMs = 100L, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = newChip @@ -68,7 +69,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { OngoingActivityChipModel.Shown.IconOnly( icon = createIcon(R.drawable.ic_hotspot), colors = ColorsModel.Themed, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = newerChip @@ -89,7 +91,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { icon = createIcon(R.drawable.ic_cake), colors = ColorsModel.Themed, startTimeMs = 100L, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = shownChip @@ -129,7 +132,8 @@ class ChipTransitionHelperTest : SysuiTestCase() { icon = createIcon(R.drawable.ic_cake), colors = ColorsModel.Themed, startTimeMs = 100L, - onClickListener = null, + onClickListenerLegacy = null, + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) inputChipFlow.value = shownChip diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt index e3510f5ce280..fc3af11c30b3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -23,14 +25,19 @@ import com.android.internal.jank.Cuj import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.log.logcatLogBuffer import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import kotlin.test.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -53,8 +60,11 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME) fun createDialogLaunchOnClickListener_showsDialogOnClick() { val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test") val clickListener = @@ -68,11 +78,23 @@ class OngoingActivityChipViewModelTest : SysuiTestCase() { clickListener.onClick(chipView) verify(dialogTransitionAnimator) - .showFromView( - eq(mockSystemUIDialog), - eq(chipBackgroundView), - eq(cuj), - anyBoolean(), + .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), eq(cuj), anyBoolean()) + } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME) + fun createDialogLaunchOnClickCallback_showsDialogOnClick() { + val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test") + val clickCallback = + createDialogLaunchOnClickCallback( + dialogDelegate, + dialogTransitionAnimator, + cuj, + logcatLogBuffer("OngoingActivityChipViewModelTest"), + "tag", ) + + clickCallback.invoke(mockExpandable) + verify(dialogTransitionAnimator).show(eq(mockSystemUIDialog), any(), anyBoolean()) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 42358cce59a2..a4b6a841d61b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -26,6 +26,7 @@ import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -44,6 +45,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel @@ -91,6 +93,8 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest = kosmos.ongoingActivityChipsViewModel @@ -294,7 +298,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { // WHEN screen record gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation @@ -315,7 +325,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { // WHEN media projection gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation @@ -330,6 +346,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { fun getStopActionFromDialog( latest: OngoingActivityChipModel?, chipView: View, + expandable: Expandable, dialog: SystemUIDialog, kosmos: Kosmos, ): DialogInterface.OnClickListener { @@ -349,9 +366,17 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { .create(any<SystemUIDialog.Delegate>()) whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>())) .thenThrow(PackageManager.NameNotFoundException()) - // Click the chip so that we open the dialog and we fill in [dialogStopAction] - val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) - clickListener!!.onClick(chipView) + + if (StatusBarChipsModernization.isEnabled) { + val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior + (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick( + expandable + ) + } else { + val clickListener = + ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy) + clickListener!!.onClick(chipView) + } return dialogStopAction } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 0f42f29e76ee..28f360108e50 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -25,6 +25,7 @@ import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Expandable import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -104,6 +105,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) .thenReturn(chipBackgroundView) } + private val mockExpandable: Expandable = + mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } private val underTest by lazy { kosmos.ongoingActivityChipsViewModel } @@ -679,7 +682,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // WHEN screen record gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation @@ -700,7 +709,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // WHEN media projection gets stopped via dialog val dialogStopAction = - getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos) + getStopActionFromDialog( + latest, + chipView, + mockExpandable, + mockSystemUIDialog, + kosmos, + ) dialogStopAction.onClick(mock<DialogInterface>(), 0) // THEN the chip is immediately hidden with no animation diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 4ef9792a2ad9..0df1073ca553 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -1383,7 +1383,6 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_NOTIFICATIONS_DISMISS_PRUNED_SUMMARIES) public void testDismissNotificationsIncludesPrunedParents() { // GIVEN a collection with 2 groups; one has a single child, one has two. mCollection.addNotificationDismissInterceptor(mInterceptor1); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt index 739a9c956178..9dfc922eb7d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.headsup import android.app.Notification +import android.app.Notification.FLAG_PROMOTED_ONGOING import android.app.PendingIntent import android.app.Person import android.os.Handler @@ -677,10 +678,12 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testIsSticky_rowPinnedAndExpanded_true() { - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val row = testHelper.createRow() - row.setPinnedStatus(PinnedStatus.PinnedBySystem) + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun testIsSticky_promotedAndExpanded_notifChipsFlagOff_true() { + val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + notif.flags = FLAG_PROMOTED_ONGOING + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, notif) + val row = testHelper.createRow().apply { setPinnedStatus(PinnedStatus.PinnedBySystem) } notifEntry.row = row underTest.showNotification(notifEntry) @@ -692,6 +695,23 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testIsSticky_promotedAndExpanded_notifChipsFlagOn_false() { + val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + notif.flags = FLAG_PROMOTED_ONGOING + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, notif) + val row = testHelper.createRow().apply { setPinnedStatus(PinnedStatus.PinnedBySystem) } + notifEntry.row = row + + underTest.showNotification(notifEntry) + + val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.setExpanded(true) + + assertThat(underTest.isSticky(notifEntry.key)).isFalse() + } + + @Test fun testIsSticky_remoteInputActive_true() { val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index 92271198cac0..8d90d38a9eca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -212,7 +212,11 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { } @Test - @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + @EnableFlags( + PromotedNotificationUi.FLAG_NAME, + StatusBarNotifChips.FLAG_NAME, + android.app.Flags.FLAG_API_RICH_ONGOING, + ) fun extractContent_fromProgressStyle() { val entry = createEntry { setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 650fa7ce46de..58856d970711 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -44,7 +46,9 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.animation.back.BackAnimationSpec; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.FakeDisplayTracker; import org.junit.Before; import org.junit.Rule; @@ -68,6 +72,7 @@ public class SystemUIDialogTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private SystemUIDialog.Delegate mDelegate; + private SysUiState mSysUiState; // TODO(b/292141694): build out Ravenwood support for DeviceFlagsValueProvider // Ravenwood already has solid support for SetFlagsRule, but CheckFlagsRule will be added soon @@ -78,7 +83,9 @@ public class SystemUIDialogTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - + KosmosJavaAdapter kosmos = new KosmosJavaAdapter(this); + FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); + mSysUiState = new SysUiState(displayTracker, kosmos.getSceneContainerPlugin()); mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher); when(mDelegate.getBackAnimationSpec(ArgumentMatchers.any())) .thenReturn(mock(BackAnimationSpec.class)); @@ -173,6 +180,30 @@ public class SystemUIDialogTest extends SysuiTestCase { assertThat(calledStop.get()).isTrue(); } + /** Regression test for b/386871258 */ + @Test + public void sysuiStateUpdated() { + SystemUIDialog dialog1 = + createDialogWithDelegate(mContext, mDelegate, /* shouldAcsDismissDialog */ true); + SystemUIDialog dialog2 = + createDialogWithDelegate(mContext, mDelegate, /* shouldAcsDismissDialog */ true); + + dialog1.show(); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isTrue(); + + dialog2.show(); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isTrue(); + + dialog2.dismiss(); + // explicitly call onWindowFocusChanged to simulate dialog 1 regaining focus + dialog1.onWindowFocusChanged(/* hasFocus= */ true); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isTrue(); + + dialog1.dismiss(); + assertThat((mSysUiState.getFlags() & SYSUI_STATE_DIALOG_SHOWING) != 0).isFalse(); + } + + @Test public void delegateIsCalled_inCorrectOrder() { Configuration configuration = new Configuration(); @@ -198,7 +229,7 @@ public class SystemUIDialogTest extends SysuiTestCase { SystemUIDialog.Factory factory = new SystemUIDialog.Factory( getContext(), Dependency.get(SystemUIDialogManager.class), - Dependency.get(SysUiState.class), + mSysUiState, Dependency.get(BroadcastDispatcher.class), Dependency.get(DialogTransitionAnimator.class) ); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 6da06a36f63d..02135f6a7836 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository @@ -169,6 +170,27 @@ class OngoingCallControllerTest : SysuiTestCase() { } @Test + fun interactorHasOngoingCallNotif_repoHasPromotedContent() = + testScope.runTest { + val promotedContent = PromotedNotificationContentModel.Builder("ongoingNotif").build() + setNotifOnRepo( + activeNotificationModel( + key = "ongoingNotif", + callType = CallType.Ongoing, + uid = CALL_UID, + statusBarChipIcon = mock(), + whenTime = 567, + promotedContent = promotedContent, + ) + ) + + val repoState = ongoingCallRepository.ongoingCallState.value + assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((repoState as OngoingCallModel.InCall).promotedContent) + .isEqualTo(promotedContent) + } + + @Test fun notifRepoHasOngoingCallNotif_isOngoingCallNotif_windowControllerUpdated() { setCallNotifOnRepo() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index 8fb95e843ec1..14263c4b1b9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore @@ -69,33 +70,37 @@ class OngoingCallInteractorTest : SysuiTestCase() { } @Test - fun ongoingCallNotification_setsNotificationIconAndIntent() = + fun ongoingCallNotification_setsAllFields() = kosmos.runTest { val latest by collectLastValue(underTest.ongoingCallState) // Set up notification with icon view and intent val testIconView: StatusBarIconView = mock() val testIntent: PendingIntent = mock() + val testPromotedContent = + PromotedNotificationContentModel.Builder("promotedCall").build() repository.activeNotifications.value = ActiveNotificationsStore.Builder() .apply { addIndividualNotif( activeNotificationModel( - key = "notif1", + key = "promotedCall", whenTime = 1000L, callType = CallType.Ongoing, statusBarChipIcon = testIconView, contentIntent = testIntent, + promotedContent = testPromotedContent, ) ) } .build() - // Verify model is InCall and has the correct icon and intent. + // Verify model is InCall and has the correct icon, intent, and promoted content. assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) val model = latest as OngoingCallModel.InCall assertThat(model.notificationIconView).isSameInstanceAs(testIconView) assertThat(model.intent).isSameInstanceAs(testIntent) + assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt index 937f333b0065..a1c910d48cef 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt @@ -24,6 +24,8 @@ import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChip import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel +import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel +import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -51,28 +53,15 @@ class FakeHomeStatusBarViewModel( override val shouldShowOperatorNameView = MutableStateFlow(false) override val isClockVisible = - MutableStateFlow( - HomeStatusBarViewModel.VisibilityModel( - visibility = View.GONE, - shouldAnimateChange = false, - ) - ) + MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false)) override val isNotificationIconContainerVisible = - MutableStateFlow( - HomeStatusBarViewModel.VisibilityModel( - visibility = View.GONE, - shouldAnimateChange = false, - ) - ) + MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false)) override val systemInfoCombinedVis = MutableStateFlow( - HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel( - HomeStatusBarViewModel.VisibilityModel( - visibility = View.GONE, - shouldAnimateChange = false, - ), + SystemInfoCombinedVisibilityModel( + VisibilityModel(visibility = View.GONE, shouldAnimateChange = false), Idle, ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index 03abcf850d26..e74d009bb909 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -83,12 +83,11 @@ import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel +import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Before @@ -423,8 +422,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { fun areNotificationsLightsOut_requiresFlagEnabled() = kosmos.runTest { assertLogsWtf { - val flow = underTest.areNotificationsLightsOut - assertThat(flow).isEqualTo(emptyFlow<Boolean>()) + val latest by collectLastValue(underTest.areNotificationsLightsOut) + // Nothing is emitted + assertThat(latest).isNull() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt index b2378d2c3aae..2d6315014164 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt @@ -66,13 +66,14 @@ class ModesDialogDelegateTest : SysuiTestCase() { whenever( mockDialogTransitionAnimator.createActivityTransitionController( any<SystemUIDialog>(), - eq(null) + eq(null), ) ) .thenReturn(mockAnimationController) underTest = ModesDialogDelegate( + context, kosmos.systemUIDialogFactory, mockDialogTransitionAnimator, activityStarter, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 55be9f79e598..ca98cbf20c3a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -140,6 +140,11 @@ public interface ActivityStarter { void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable String customMessage); + /** Posts a start activity intent that dismisses keyguard. */ + void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityTransitionAnimator.Controller animationController, + @Nullable String customMessage, + @Nullable UserHandle userHandle); void postStartActivityDismissingKeyguard(PendingIntent intent); /** diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 02b2bcf8e40d..e152c98a93e5 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -58,3 +58,8 @@ static *** v(...); } -maximumremovedandroidloglevel 2 + +#Keep the R +-keepclassmembers class com.android.systemui.customization.R$* { + public static <fields>; +} diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_button_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_button_container.xml new file mode 100644 index 000000000000..32aacf6522e7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_media_connecting_button_container.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 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="88dp" + android:height="56dp" + android:viewportHeight="56" + android:viewportWidth="88"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:scaleX="1.05905" + android:scaleY="1.0972" + android:translateX="43.528999999999996" + android:translateY="27.898" /> + <group + android:name="_R_G_L_0_G" + android:pivotX="0.493" + android:pivotY="0.124" + android:scaleX="1.05905" + android:scaleY="1.0972" + android:translateX="43.528999999999996" + android:translateY="27.898"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#3d90ff" + android:fillType="nonZero" + android:pathData=" M34.49 -5.75 C34.49,-5.75 34.49,6 34.49,6 C34.49,14.84 27.32,22 18.49,22 C18.49,22 -17.5,22 -17.5,22 C-26.34,22 -33.5,14.84 -33.5,6 C-33.5,6 -33.5,-5.75 -33.5,-5.75 C-33.5,-14.59 -26.34,-21.75 -17.5,-21.75 C-17.5,-21.75 18.49,-21.75 18.49,-21.75 C27.32,-21.75 34.49,-14.59 34.49,-5.75c " /> + </group> + </group> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml deleted file mode 100644 index f8c0fa04cd39..000000000000 --- a/packages/SystemUI/res/drawable/ic_media_connecting_status_container.xml +++ /dev/null @@ -1,199 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2025 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. - --> -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:aapt="http://schemas.android.com/aapt"> - <target android:name="_R_G_L_1_G"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="scaleX" - android:startOffset="1000" - android:valueFrom="0.45561" - android:valueTo="0.69699" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="83" - android:propertyName="scaleY" - android:startOffset="1000" - android:valueFrom="0.6288400000000001" - android:valueTo="0.81618" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleX" - android:startOffset="1083" - android:valueFrom="0.69699" - android:valueTo="1.05905" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleY" - android:startOffset="1083" - android:valueFrom="0.81618" - android:valueTo="1.0972" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="500" - android:propertyName="rotation" - android:startOffset="0" - android:valueFrom="90" - android:valueTo="135" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="500" - android:propertyName="rotation" - android:startOffset="500" - android:valueFrom="135" - android:valueTo="180" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_0_G"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="scaleX" - android:startOffset="1000" - android:valueFrom="0.0434" - android:valueTo="0.05063" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="83" - android:propertyName="scaleY" - android:startOffset="1000" - android:valueFrom="0.0434" - android:valueTo="0.042350000000000006" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,0.15 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleX" - android:startOffset="1083" - android:valueFrom="0.05063" - android:valueTo="0.06146" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="417" - android:propertyName="scaleY" - android:startOffset="1083" - android:valueFrom="0.042350000000000006" - android:valueTo="0.040780000000000004" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.05,0.7 0.1,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="time_group"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="1017" - android:propertyName="translateX" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType" /> - </set> - </aapt:attr> - </target> - <aapt:attr name="android:drawable"> - <vector - android:width="88dp" - android:height="56dp" - android:viewportHeight="56" - android:viewportWidth="88"> - <group android:name="_R_G"> - <group - android:name="_R_G_L_1_G" - android:pivotX="0.493" - android:pivotY="0.124" - android:scaleX="1.05905" - android:scaleY="1.0972" - android:translateX="43.528999999999996" - android:translateY="27.898"> - <path - android:name="_R_G_L_1_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#3d90ff" - android:fillType="nonZero" - android:pathData=" M34.47 0.63 C34.47,0.63 34.42,0.64 34.42,0.64 C33.93,12.88 24.69,21.84 13.06,21.97 C13.06,21.97 -12.54,21.97 -12.54,21.97 C-23.11,21.84 -33.38,13.11 -33.52,-0.27 C-33.52,-0.27 -33.52,-0.05 -33.52,-0.05 C-33.5,-13.21 -21.73,-21.76 -12.9,-21.76 C-12.9,-21.76 14.59,-21.76 14.59,-21.76 C24.81,-21.88 34.49,-10.58 34.47,0.63c " /> - </group> - <group - android:name="_R_G_L_0_G" - android:rotation="0" - android:scaleX="0.06146" - android:scaleY="0.040780000000000004" - android:translateX="44" - android:translateY="28"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#3d90ff" - android:fillType="nonZero" - android:pathData=" M-0.65 -437.37 C-0.65,-437.37 8.33,-437.66 8.33,-437.66 C8.33,-437.66 17.31,-437.95 17.31,-437.95 C17.31,-437.95 26.25,-438.78 26.25,-438.78 C26.25,-438.78 35.16,-439.95 35.16,-439.95 C35.16,-439.95 44.07,-441.11 44.07,-441.11 C44.07,-441.11 52.85,-443 52.85,-443 C52.85,-443 61.6,-445.03 61.6,-445.03 C61.6,-445.03 70.35,-447.09 70.35,-447.09 C70.35,-447.09 78.91,-449.83 78.91,-449.83 C78.91,-449.83 87.43,-452.67 87.43,-452.67 C87.43,-452.67 95.79,-455.97 95.79,-455.97 C95.79,-455.97 104.11,-459.35 104.11,-459.35 C104.11,-459.35 112.36,-462.93 112.36,-462.93 C112.36,-462.93 120.6,-466.51 120.6,-466.51 C120.6,-466.51 128.84,-470.09 128.84,-470.09 C128.84,-470.09 137.09,-473.67 137.09,-473.67 C137.09,-473.67 145.49,-476.84 145.49,-476.84 C145.49,-476.84 153.9,-480.01 153.9,-480.01 C153.9,-480.01 162.31,-483.18 162.31,-483.18 C162.31,-483.18 170.98,-485.54 170.98,-485.54 C170.98,-485.54 179.66,-487.85 179.66,-487.85 C179.66,-487.85 188.35,-490.15 188.35,-490.15 C188.35,-490.15 197.22,-491.58 197.22,-491.58 C197.22,-491.58 206.09,-493.01 206.09,-493.01 C206.09,-493.01 214.98,-494.28 214.98,-494.28 C214.98,-494.28 223.95,-494.81 223.95,-494.81 C223.95,-494.81 232.93,-495.33 232.93,-495.33 C232.93,-495.33 241.9,-495.5 241.9,-495.5 C241.9,-495.5 250.88,-495.13 250.88,-495.13 C250.88,-495.13 259.86,-494.75 259.86,-494.75 C259.86,-494.75 268.78,-493.78 268.78,-493.78 C268.78,-493.78 277.68,-492.52 277.68,-492.52 C277.68,-492.52 286.57,-491.26 286.57,-491.26 C286.57,-491.26 295.31,-489.16 295.31,-489.16 C295.31,-489.16 304.04,-487.04 304.04,-487.04 C304.04,-487.04 312.7,-484.65 312.7,-484.65 C312.7,-484.65 321.19,-481.72 321.19,-481.72 C321.19,-481.72 329.68,-478.78 329.68,-478.78 C329.68,-478.78 337.96,-475.31 337.96,-475.31 C337.96,-475.31 346.14,-471.59 346.14,-471.59 C346.14,-471.59 354.3,-467.82 354.3,-467.82 C354.3,-467.82 362.11,-463.38 362.11,-463.38 C362.11,-463.38 369.92,-458.93 369.92,-458.93 C369.92,-458.93 377.53,-454.17 377.53,-454.17 C377.53,-454.17 384.91,-449.04 384.91,-449.04 C384.91,-449.04 392.29,-443.91 392.29,-443.91 C392.29,-443.91 399.26,-438.24 399.26,-438.24 C399.26,-438.24 406.15,-432.48 406.15,-432.48 C406.15,-432.48 412.92,-426.57 412.92,-426.57 C412.92,-426.57 419.27,-420.22 419.27,-420.22 C419.27,-420.22 425.62,-413.87 425.62,-413.87 C425.62,-413.87 431.61,-407.18 431.61,-407.18 C431.61,-407.18 437.38,-400.29 437.38,-400.29 C437.38,-400.29 443.14,-393.39 443.14,-393.39 C443.14,-393.39 448.27,-386.01 448.27,-386.01 C448.27,-386.01 453.4,-378.64 453.4,-378.64 C453.4,-378.64 458.26,-371.09 458.26,-371.09 C458.26,-371.09 462.71,-363.28 462.71,-363.28 C462.71,-363.28 467.16,-355.47 467.16,-355.47 C467.16,-355.47 471.03,-347.37 471.03,-347.37 C471.03,-347.37 474.75,-339.19 474.75,-339.19 C474.75,-339.19 478.34,-330.95 478.34,-330.95 C478.34,-330.95 481.28,-322.46 481.28,-322.46 C481.28,-322.46 484.21,-313.97 484.21,-313.97 C484.21,-313.97 486.72,-305.35 486.72,-305.35 C486.72,-305.35 488.84,-296.62 488.84,-296.62 C488.84,-296.62 490.96,-287.88 490.96,-287.88 C490.96,-287.88 492.33,-279.01 492.33,-279.01 C492.33,-279.01 493.59,-270.11 493.59,-270.11 C493.59,-270.11 494.69,-261.2 494.69,-261.2 C494.69,-261.2 495.07,-252.22 495.07,-252.22 C495.07,-252.22 495.44,-243.24 495.44,-243.24 C495.44,-243.24 495.41,-234.27 495.41,-234.27 C495.41,-234.27 494.88,-225.29 494.88,-225.29 C494.88,-225.29 494.35,-216.32 494.35,-216.32 C494.35,-216.32 493.22,-207.42 493.22,-207.42 C493.22,-207.42 491.79,-198.55 491.79,-198.55 C491.79,-198.55 490.36,-189.68 490.36,-189.68 C490.36,-189.68 488.19,-180.96 488.19,-180.96 C488.19,-180.96 485.88,-172.28 485.88,-172.28 C485.88,-172.28 483.56,-163.6 483.56,-163.6 C483.56,-163.6 480.48,-155.16 480.48,-155.16 C480.48,-155.16 477.31,-146.75 477.31,-146.75 C477.31,-146.75 474.14,-138.34 474.14,-138.34 C474.14,-138.34 470.62,-130.07 470.62,-130.07 C470.62,-130.07 467.04,-121.83 467.04,-121.83 C467.04,-121.83 463.46,-113.59 463.46,-113.59 C463.46,-113.59 459.88,-105.35 459.88,-105.35 C459.88,-105.35 456.54,-97.01 456.54,-97.01 C456.54,-97.01 453.37,-88.6 453.37,-88.6 C453.37,-88.6 450.21,-80.19 450.21,-80.19 C450.21,-80.19 447.68,-71.57 447.68,-71.57 C447.68,-71.57 445.36,-62.89 445.36,-62.89 C445.36,-62.89 443.04,-54.21 443.04,-54.21 C443.04,-54.21 441.54,-45.35 441.54,-45.35 C441.54,-45.35 440.09,-36.48 440.09,-36.48 C440.09,-36.48 438.78,-27.6 438.78,-27.6 C438.78,-27.6 438.19,-18.63 438.19,-18.63 C438.19,-18.63 437.61,-9.66 437.61,-9.66 C437.61,-9.66 437.36,-0.69 437.36,-0.69 C437.36,-0.69 437.65,8.29 437.65,8.29 C437.65,8.29 437.95,17.27 437.95,17.27 C437.95,17.27 438.77,26.21 438.77,26.21 C438.77,26.21 439.94,35.12 439.94,35.12 C439.94,35.12 441.11,44.03 441.11,44.03 C441.11,44.03 442.99,52.81 442.99,52.81 C442.99,52.81 445.02,61.57 445.02,61.57 C445.02,61.57 447.07,70.31 447.07,70.31 C447.07,70.31 449.82,78.87 449.82,78.87 C449.82,78.87 452.65,87.4 452.65,87.4 C452.65,87.4 455.96,95.75 455.96,95.75 C455.96,95.75 459.33,104.08 459.33,104.08 C459.33,104.08 462.91,112.32 462.91,112.32 C462.91,112.32 466.49,120.57 466.49,120.57 C466.49,120.57 470.07,128.81 470.07,128.81 C470.07,128.81 473.65,137.05 473.65,137.05 C473.65,137.05 476.82,145.46 476.82,145.46 C476.82,145.46 479.99,153.87 479.99,153.87 C479.99,153.87 483.17,162.28 483.17,162.28 C483.17,162.28 485.52,170.94 485.52,170.94 C485.52,170.94 487.84,179.63 487.84,179.63 C487.84,179.63 490.14,188.31 490.14,188.31 C490.14,188.31 491.57,197.18 491.57,197.18 C491.57,197.18 493,206.06 493,206.06 C493,206.06 494.27,214.95 494.27,214.95 C494.27,214.95 494.8,223.92 494.8,223.92 C494.8,223.92 495.33,232.89 495.33,232.89 C495.33,232.89 495.5,241.86 495.5,241.86 C495.5,241.86 495.12,250.84 495.12,250.84 C495.12,250.84 494.75,259.82 494.75,259.82 C494.75,259.82 493.78,268.74 493.78,268.74 C493.78,268.74 492.52,277.64 492.52,277.64 C492.52,277.64 491.27,286.54 491.27,286.54 C491.27,286.54 489.16,295.27 489.16,295.27 C489.16,295.27 487.05,304.01 487.05,304.01 C487.05,304.01 484.66,312.66 484.66,312.66 C484.66,312.66 481.73,321.16 481.73,321.16 C481.73,321.16 478.79,329.65 478.79,329.65 C478.79,329.65 475.32,337.93 475.32,337.93 C475.32,337.93 471.6,346.11 471.6,346.11 C471.6,346.11 467.84,354.27 467.84,354.27 C467.84,354.27 463.39,362.08 463.39,362.08 C463.39,362.08 458.94,369.89 458.94,369.89 C458.94,369.89 454.19,377.5 454.19,377.5 C454.19,377.5 449.06,384.88 449.06,384.88 C449.06,384.88 443.93,392.26 443.93,392.26 C443.93,392.26 438.26,399.23 438.26,399.23 C438.26,399.23 432.5,406.12 432.5,406.12 C432.5,406.12 426.6,412.89 426.6,412.89 C426.6,412.89 420.24,419.24 420.24,419.24 C420.24,419.24 413.89,425.6 413.89,425.6 C413.89,425.6 407.2,431.59 407.2,431.59 C407.2,431.59 400.31,437.36 400.31,437.36 C400.31,437.36 393.42,443.12 393.42,443.12 C393.42,443.12 386.04,448.25 386.04,448.25 C386.04,448.25 378.66,453.38 378.66,453.38 C378.66,453.38 371.11,458.24 371.11,458.24 C371.11,458.24 363.31,462.69 363.31,462.69 C363.31,462.69 355.5,467.14 355.5,467.14 C355.5,467.14 347.4,471.02 347.4,471.02 C347.4,471.02 339.22,474.73 339.22,474.73 C339.22,474.73 330.99,478.33 330.99,478.33 C330.99,478.33 322.49,481.27 322.49,481.27 C322.49,481.27 314,484.2 314,484.2 C314,484.2 305.38,486.71 305.38,486.71 C305.38,486.71 296.65,488.83 296.65,488.83 C296.65,488.83 287.91,490.95 287.91,490.95 C287.91,490.95 279.04,492.33 279.04,492.33 C279.04,492.33 270.14,493.59 270.14,493.59 C270.14,493.59 261.23,494.69 261.23,494.69 C261.23,494.69 252.25,495.07 252.25,495.07 C252.25,495.07 243.28,495.44 243.28,495.44 C243.28,495.44 234.3,495.41 234.3,495.41 C234.3,495.41 225.33,494.88 225.33,494.88 C225.33,494.88 216.36,494.35 216.36,494.35 C216.36,494.35 207.45,493.23 207.45,493.23 C207.45,493.23 198.58,491.8 198.58,491.8 C198.58,491.8 189.71,490.37 189.71,490.37 C189.71,490.37 180.99,488.21 180.99,488.21 C180.99,488.21 172.31,485.89 172.31,485.89 C172.31,485.89 163.63,483.57 163.63,483.57 C163.63,483.57 155.19,480.5 155.19,480.5 C155.19,480.5 146.78,477.32 146.78,477.32 C146.78,477.32 138.37,474.15 138.37,474.15 C138.37,474.15 130.11,470.63 130.11,470.63 C130.11,470.63 121.86,467.06 121.86,467.06 C121.86,467.06 113.62,463.48 113.62,463.48 C113.62,463.48 105.38,459.9 105.38,459.9 C105.38,459.9 97.04,456.56 97.04,456.56 C97.04,456.56 88.63,453.39 88.63,453.39 C88.63,453.39 80.22,450.22 80.22,450.22 C80.22,450.22 71.6,447.7 71.6,447.7 C71.6,447.7 62.92,445.37 62.92,445.37 C62.92,445.37 54.24,443.05 54.24,443.05 C54.24,443.05 45.38,441.55 45.38,441.55 C45.38,441.55 36.52,440.1 36.52,440.1 C36.52,440.1 27.63,438.78 27.63,438.78 C27.63,438.78 18.66,438.2 18.66,438.2 C18.66,438.2 9.7,437.61 9.7,437.61 C9.7,437.61 0.72,437.36 0.72,437.36 C0.72,437.36 -8.26,437.65 -8.26,437.65 C-8.26,437.65 -17.24,437.95 -17.24,437.95 C-17.24,437.95 -26.18,438.77 -26.18,438.77 C-26.18,438.77 -35.09,439.94 -35.09,439.94 C-35.09,439.94 -44,441.1 -44,441.1 C-44,441.1 -52.78,442.98 -52.78,442.98 C-52.78,442.98 -61.53,445.02 -61.53,445.02 C-61.53,445.02 -70.28,447.07 -70.28,447.07 C-70.28,447.07 -78.84,449.81 -78.84,449.81 C-78.84,449.81 -87.37,452.64 -87.37,452.64 C-87.37,452.64 -95.72,455.95 -95.72,455.95 C-95.72,455.95 -104.05,459.32 -104.05,459.32 C-104.05,459.32 -112.29,462.9 -112.29,462.9 C-112.29,462.9 -120.53,466.48 -120.53,466.48 C-120.53,466.48 -128.78,470.06 -128.78,470.06 C-128.78,470.06 -137.02,473.63 -137.02,473.63 C-137.02,473.63 -145.43,476.81 -145.43,476.81 C-145.43,476.81 -153.84,479.98 -153.84,479.98 C-153.84,479.98 -162.24,483.15 -162.24,483.15 C-162.24,483.15 -170.91,485.52 -170.91,485.52 C-170.91,485.52 -179.59,487.83 -179.59,487.83 C-179.59,487.83 -188.28,490.13 -188.28,490.13 C-188.28,490.13 -197.15,491.56 -197.15,491.56 C-197.15,491.56 -206.02,492.99 -206.02,492.99 C-206.02,492.99 -214.91,494.27 -214.91,494.27 C-214.91,494.27 -223.88,494.8 -223.88,494.8 C-223.88,494.8 -232.85,495.33 -232.85,495.33 C-232.85,495.33 -241.83,495.5 -241.83,495.5 C-241.83,495.5 -250.81,495.13 -250.81,495.13 C-250.81,495.13 -259.79,494.75 -259.79,494.75 C-259.79,494.75 -268.71,493.79 -268.71,493.79 C-268.71,493.79 -277.61,492.53 -277.61,492.53 C-277.61,492.53 -286.51,491.27 -286.51,491.27 C-286.51,491.27 -295.24,489.17 -295.24,489.17 C-295.24,489.17 -303.98,487.06 -303.98,487.06 C-303.98,487.06 -312.63,484.67 -312.63,484.67 C-312.63,484.67 -321.12,481.74 -321.12,481.74 C-321.12,481.74 -329.62,478.8 -329.62,478.8 C-329.62,478.8 -337.9,475.33 -337.9,475.33 C-337.9,475.33 -346.08,471.62 -346.08,471.62 C-346.08,471.62 -354.24,467.85 -354.24,467.85 C-354.24,467.85 -362.05,463.41 -362.05,463.41 C-362.05,463.41 -369.86,458.96 -369.86,458.96 C-369.86,458.96 -377.47,454.21 -377.47,454.21 C-377.47,454.21 -384.85,449.08 -384.85,449.08 C-384.85,449.08 -392.23,443.95 -392.23,443.95 C-392.23,443.95 -399.2,438.29 -399.2,438.29 C-399.2,438.29 -406.09,432.52 -406.09,432.52 C-406.09,432.52 -412.86,426.62 -412.86,426.62 C-412.86,426.62 -419.22,420.27 -419.22,420.27 C-419.22,420.27 -425.57,413.91 -425.57,413.91 C-425.57,413.91 -431.57,407.23 -431.57,407.23 C-431.57,407.23 -437.33,400.34 -437.33,400.34 C-437.33,400.34 -443.1,393.44 -443.1,393.44 C-443.1,393.44 -448.23,386.07 -448.23,386.07 C-448.23,386.07 -453.36,378.69 -453.36,378.69 C-453.36,378.69 -458.23,371.15 -458.23,371.15 C-458.23,371.15 -462.67,363.33 -462.67,363.33 C-462.67,363.33 -467.12,355.53 -467.12,355.53 C-467.12,355.53 -471,347.43 -471,347.43 C-471,347.43 -474.72,339.25 -474.72,339.25 C-474.72,339.25 -478.32,331.02 -478.32,331.02 C-478.32,331.02 -481.25,322.52 -481.25,322.52 C-481.25,322.52 -484.19,314.03 -484.19,314.03 C-484.19,314.03 -486.71,305.42 -486.71,305.42 C-486.71,305.42 -488.82,296.68 -488.82,296.68 C-488.82,296.68 -490.94,287.95 -490.94,287.95 C-490.94,287.95 -492.32,279.07 -492.32,279.07 C-492.32,279.07 -493.58,270.18 -493.58,270.18 C-493.58,270.18 -494.69,261.27 -494.69,261.27 C-494.69,261.27 -495.07,252.29 -495.07,252.29 C-495.07,252.29 -495.44,243.31 -495.44,243.31 C-495.44,243.31 -495.42,234.33 -495.42,234.33 C-495.42,234.33 -494.89,225.36 -494.89,225.36 C-494.89,225.36 -494.36,216.39 -494.36,216.39 C-494.36,216.39 -493.23,207.49 -493.23,207.49 C-493.23,207.49 -491.8,198.61 -491.8,198.61 C-491.8,198.61 -490.37,189.74 -490.37,189.74 C-490.37,189.74 -488.22,181.02 -488.22,181.02 C-488.22,181.02 -485.9,172.34 -485.9,172.34 C-485.9,172.34 -483.58,163.66 -483.58,163.66 C-483.58,163.66 -480.51,155.22 -480.51,155.22 C-480.51,155.22 -477.34,146.81 -477.34,146.81 C-477.34,146.81 -474.17,138.41 -474.17,138.41 C-474.17,138.41 -470.65,130.14 -470.65,130.14 C-470.65,130.14 -467.07,121.9 -467.07,121.9 C-467.07,121.9 -463.49,113.65 -463.49,113.65 C-463.49,113.65 -459.91,105.41 -459.91,105.41 C-459.91,105.41 -456.57,97.07 -456.57,97.07 C-456.57,97.07 -453.4,88.66 -453.4,88.66 C-453.4,88.66 -450.23,80.25 -450.23,80.25 C-450.23,80.25 -447.7,71.64 -447.7,71.64 C-447.7,71.64 -445.38,62.96 -445.38,62.96 C-445.38,62.96 -443.06,54.28 -443.06,54.28 C-443.06,54.28 -441.56,45.42 -441.56,45.42 C-441.56,45.42 -440.1,36.55 -440.1,36.55 C-440.1,36.55 -438.78,27.67 -438.78,27.67 C-438.78,27.67 -438.2,18.7 -438.2,18.7 C-438.2,18.7 -437.62,9.73 -437.62,9.73 C-437.62,9.73 -437.36,0.76 -437.36,0.76 C-437.36,0.76 -437.66,-8.22 -437.66,-8.22 C-437.66,-8.22 -437.95,-17.2 -437.95,-17.2 C-437.95,-17.2 -438.77,-26.14 -438.77,-26.14 C-438.77,-26.14 -439.93,-35.05 -439.93,-35.05 C-439.93,-35.05 -441.1,-43.96 -441.1,-43.96 C-441.1,-43.96 -442.98,-52.75 -442.98,-52.75 C-442.98,-52.75 -445.01,-61.5 -445.01,-61.5 C-445.01,-61.5 -447.06,-70.25 -447.06,-70.25 C-447.06,-70.25 -449.8,-78.81 -449.8,-78.81 C-449.8,-78.81 -452.63,-87.33 -452.63,-87.33 C-452.63,-87.33 -455.94,-95.69 -455.94,-95.69 C-455.94,-95.69 -459.31,-104.02 -459.31,-104.02 C-459.31,-104.02 -462.89,-112.26 -462.89,-112.26 C-462.89,-112.26 -466.47,-120.5 -466.47,-120.5 C-466.47,-120.5 -470.05,-128.74 -470.05,-128.74 C-470.05,-128.74 -473.68,-137.12 -473.68,-137.12 C-473.68,-137.12 -476.85,-145.53 -476.85,-145.53 C-476.85,-145.53 -480.03,-153.94 -480.03,-153.94 C-480.03,-153.94 -483.2,-162.34 -483.2,-162.34 C-483.2,-162.34 -485.55,-171.02 -485.55,-171.02 C-485.55,-171.02 -487.86,-179.7 -487.86,-179.7 C-487.86,-179.7 -490.15,-188.39 -490.15,-188.39 C-490.15,-188.39 -491.58,-197.26 -491.58,-197.26 C-491.58,-197.26 -493.01,-206.13 -493.01,-206.13 C-493.01,-206.13 -494.28,-215.02 -494.28,-215.02 C-494.28,-215.02 -494.81,-223.99 -494.81,-223.99 C-494.81,-223.99 -495.33,-232.96 -495.33,-232.96 C-495.33,-232.96 -495.5,-241.94 -495.5,-241.94 C-495.5,-241.94 -495.12,-250.92 -495.12,-250.92 C-495.12,-250.92 -494.75,-259.9 -494.75,-259.9 C-494.75,-259.9 -493.78,-268.82 -493.78,-268.82 C-493.78,-268.82 -492.52,-277.72 -492.52,-277.72 C-492.52,-277.72 -491.26,-286.61 -491.26,-286.61 C-491.26,-286.61 -489.15,-295.35 -489.15,-295.35 C-489.15,-295.35 -487.03,-304.08 -487.03,-304.08 C-487.03,-304.08 -484.64,-312.73 -484.64,-312.73 C-484.64,-312.73 -481.7,-321.23 -481.7,-321.23 C-481.7,-321.23 -478.77,-329.72 -478.77,-329.72 C-478.77,-329.72 -475.29,-338 -475.29,-338 C-475.29,-338 -471.57,-346.18 -471.57,-346.18 C-471.57,-346.18 -467.8,-354.33 -467.8,-354.33 C-467.8,-354.33 -463.36,-362.14 -463.36,-362.14 C-463.36,-362.14 -458.91,-369.95 -458.91,-369.95 C-458.91,-369.95 -454.15,-377.56 -454.15,-377.56 C-454.15,-377.56 -449.02,-384.94 -449.02,-384.94 C-449.02,-384.94 -443.88,-392.32 -443.88,-392.32 C-443.88,-392.32 -438.22,-399.28 -438.22,-399.28 C-438.22,-399.28 -432.45,-406.18 -432.45,-406.18 C-432.45,-406.18 -426.55,-412.94 -426.55,-412.94 C-426.55,-412.94 -420.19,-419.3 -420.19,-419.3 C-420.19,-419.3 -413.84,-425.65 -413.84,-425.65 C-413.84,-425.65 -407.15,-431.64 -407.15,-431.64 C-407.15,-431.64 -400.26,-437.41 -400.26,-437.41 C-400.26,-437.41 -393.36,-443.16 -393.36,-443.16 C-393.36,-443.16 -385.98,-448.29 -385.98,-448.29 C-385.98,-448.29 -378.6,-453.43 -378.6,-453.43 C-378.6,-453.43 -371.05,-458.28 -371.05,-458.28 C-371.05,-458.28 -363.24,-462.73 -363.24,-462.73 C-363.24,-462.73 -355.43,-467.18 -355.43,-467.18 C-355.43,-467.18 -347.33,-471.05 -347.33,-471.05 C-347.33,-471.05 -339.15,-474.76 -339.15,-474.76 C-339.15,-474.76 -330.92,-478.35 -330.92,-478.35 C-330.92,-478.35 -322.42,-481.29 -322.42,-481.29 C-322.42,-481.29 -313.93,-484.23 -313.93,-484.23 C-313.93,-484.23 -305.31,-486.73 -305.31,-486.73 C-305.31,-486.73 -296.58,-488.85 -296.58,-488.85 C-296.58,-488.85 -287.85,-490.97 -287.85,-490.97 C-287.85,-490.97 -278.97,-492.34 -278.97,-492.34 C-278.97,-492.34 -270.07,-493.6 -270.07,-493.6 C-270.07,-493.6 -261.16,-494.7 -261.16,-494.7 C-261.16,-494.7 -252.18,-495.07 -252.18,-495.07 C-252.18,-495.07 -243.2,-495.44 -243.2,-495.44 C-243.2,-495.44 -234.23,-495.41 -234.23,-495.41 C-234.23,-495.41 -225.26,-494.88 -225.26,-494.88 C-225.26,-494.88 -216.29,-494.35 -216.29,-494.35 C-216.29,-494.35 -207.38,-493.22 -207.38,-493.22 C-207.38,-493.22 -198.51,-491.79 -198.51,-491.79 C-198.51,-491.79 -189.64,-490.36 -189.64,-490.36 C-189.64,-490.36 -180.92,-488.19 -180.92,-488.19 C-180.92,-488.19 -172.24,-485.87 -172.24,-485.87 C-172.24,-485.87 -163.56,-483.56 -163.56,-483.56 C-163.56,-483.56 -155.12,-480.47 -155.12,-480.47 C-155.12,-480.47 -146.72,-477.3 -146.72,-477.3 C-146.72,-477.3 -138.31,-474.13 -138.31,-474.13 C-138.31,-474.13 -130.04,-470.61 -130.04,-470.61 C-130.04,-470.61 -121.8,-467.03 -121.8,-467.03 C-121.8,-467.03 -113.55,-463.45 -113.55,-463.45 C-113.55,-463.45 -105.31,-459.87 -105.31,-459.87 C-105.31,-459.87 -96.97,-456.53 -96.97,-456.53 C-96.97,-456.53 -88.56,-453.37 -88.56,-453.37 C-88.56,-453.37 -80.15,-450.2 -80.15,-450.2 C-80.15,-450.2 -71.53,-447.68 -71.53,-447.68 C-71.53,-447.68 -62.85,-445.36 -62.85,-445.36 C-62.85,-445.36 -54.17,-443.04 -54.17,-443.04 C-54.17,-443.04 -45.31,-441.54 -45.31,-441.54 C-45.31,-441.54 -36.44,-440.09 -36.44,-440.09 C-36.44,-440.09 -27.56,-438.78 -27.56,-438.78 C-27.56,-438.78 -18.59,-438.19 -18.59,-438.19 C-18.59,-438.19 -9.62,-437.61 -9.62,-437.61 C-9.62,-437.61 -0.65,-437.37 -0.65,-437.37c " /> - </group> - </group> - <group android:name="time_group" /> - </vector> - </aapt:attr> -</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml b/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml new file mode 100644 index 000000000000..5f9d4212e440 --- /dev/null +++ b/packages/SystemUI/res/drawable/touchpad_tutorial_apps_icon.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2025 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:height="24dp" + android:width="24dp" + android:autoMirrored="true" + android:viewportHeight="960" + android:viewportWidth="960"> + <path android:fillColor="@android:color/white" + android:pathData="M240,800q-33,0 -56.5,-23.5T160,720q0,-33 23.5,-56.5T240,640q33,0 56.5,23.5T320,720q0,33 -23.5,56.5T240,800ZM480,800q-33,0 -56.5,-23.5T400,720q0,-33 23.5,-56.5T480,640q33,0 56.5,23.5T560,720q0,33 -23.5,56.5T480,800ZM720,800q-33,0 -56.5,-23.5T640,720q0,-33 23.5,-56.5T720,640q33,0 56.5,23.5T800,720q0,33 -23.5,56.5T720,800ZM240,560q-33,0 -56.5,-23.5T160,480q0,-33 23.5,-56.5T240,400q33,0 56.5,23.5T320,480q0,33 -23.5,56.5T240,560ZM480,560q-33,0 -56.5,-23.5T400,480q0,-33 23.5,-56.5T480,400q33,0 56.5,23.5T560,480q0,33 -23.5,56.5T480,560ZM720,560q-33,0 -56.5,-23.5T640,480q0,-33 23.5,-56.5T720,400q33,0 56.5,23.5T800,480q0,33 -23.5,56.5T720,560ZM240,320q-33,0 -56.5,-23.5T160,240q0,-33 23.5,-56.5T240,160q33,0 56.5,23.5T320,240q0,33 -23.5,56.5T240,320ZM480,320q-33,0 -56.5,-23.5T400,240q0,-33 23.5,-56.5T480,160q33,0 56.5,23.5T560,240q0,33 -23.5,56.5T480,320ZM720,320q-33,0 -56.5,-23.5T640,240q0,-33 23.5,-56.5T720,160q33,0 56.5,23.5T800,240q0,33 -23.5,56.5T720,320Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/contextual_edu_dialog.xml b/packages/SystemUI/res/layout/contextual_edu_dialog.xml index 09aa8daa217e..e83d4902d512 100644 --- a/packages/SystemUI/res/layout/contextual_edu_dialog.xml +++ b/packages/SystemUI/res/layout/contextual_edu_dialog.xml @@ -29,7 +29,7 @@ android:layout_height="wrap_content" android:contentDescription="@null" android:importantForAccessibility="no" - android:paddingRight="16dp" /> + android:paddingHorizontal="16dp" /> <TextView android:id="@+id/edu_message" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cf975d43293d..f9904e336f24 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1122,6 +1122,7 @@ <dimen name="smart_reply_button_corner_radius">8dp</dimen> <dimen name="smart_action_button_icon_size">18dp</dimen> <dimen name="smart_action_button_icon_padding">8dp</dimen> + <dimen name="smart_action_button_outline_stroke_width">2dp</dimen> <!-- A reasonable upper bound for the height of the smart reply button. The measuring code needs to start with a guess for the maximum size. Currently two-line smart reply buttons diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f2c648cb3ab0..414d3f1d17d5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3954,6 +3954,8 @@ <string name="touchpad_tutorial_home_gesture_button">Go home</string> <!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string> + <!-- Label for button opening tutorial for "switch apps" gesture on touchpad [CHAR LIMIT=NONE] --> + <string name="touchpad_tutorial_switch_apps_gesture_button">Switch apps</string> <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_done_button">Done</string> <!-- Screen title after gesture was not done correctly [CHAR LIMIT=NONE] --> @@ -3991,6 +3993,17 @@ <string name="touchpad_recent_apps_gesture_success_body">You completed the view recent apps gesture.</string> <!-- Text shown to the user after recent gesture was not done correctly [CHAR LIMIT=NONE] --> <string name="touchpad_recent_gesture_error_body">To view recent apps, swipe up and hold using three fingers on your touchpad</string> + <!-- SWITCH APPS GESTURE --> + <!-- Touchpad switch apps gesture action name in tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_action_title">Switch apps</string> + <!-- Touchpad switch apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_guidance">Swipe left or right using four fingers on your touchpad</string> + <!-- Screen title after switch apps gesture was done successfully [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_success_title">Great job!</string> + <!-- Text shown to the user after they complete switch apps gesture tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_apps_gesture_success_body">You completed the switch apps gesture.</string> + <!-- Text shown to the user after switch gesture was not done correctly [CHAR LIMIT=NONE] --> + <string name="touchpad_switch_gesture_error_body">Swipe left or right using four fingers on your touchpad to switch apps</string> <!-- KEYBOARD TUTORIAL--> <!-- Action key tutorial title [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 83ca496dbef2..d363e524a9f2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -19,10 +19,11 @@ package com.android.systemui.shared.recents; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; +import android.os.IRemoteCallback; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; -// Next ID: 34 +// Next ID: 38 oneway interface IOverviewProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -137,4 +138,20 @@ oneway interface IOverviewProxy { * Sent when {@link TaskbarDelegate#appTransitionPending} is called. */ void appTransitionPending(boolean pending) = 34; + + /** + * Sent right after OverviewProxy calls unbindService() on the TouchInteractionService. + * TouchInteractionService is expected to send the reply once it has finished cleaning up. + */ + void onUnbind(IRemoteCallback reply) = 35; + + /** + * Sent when {@link TaskbarDelegate#onDisplayReady} is called. + */ + void onDisplayReady(int displayId) = 36; + + /** + * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called. + */ + void onDisplayRemoved(int displayId) = 37; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java index caf043a1b1be..b2f3df60c82b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java @@ -200,7 +200,15 @@ public class FullscreenMagnificationController implements ComponentCallbacks { valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(@NonNull Animator animation) { - mHandler.post(() -> setState(ENABLED)); + // This could be called when the animation ends or is canceled. Therefore, we need + // to check the state of fullscreen magnification for the following actions. We only + // update the state to ENABLED when the previous state is ENABLING which implies + // fullscreen magnification is experiencing an ongoing create border process. + mHandler.post(() -> { + if (getState() == ENABLING) { + setState(ENABLED); + } + }); }}); return valueAnimator; } @@ -221,7 +229,14 @@ public class FullscreenMagnificationController implements ComponentCallbacks { valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(@NonNull Animator animation) { - mHandler.post(() -> cleanUpBorder()); + // This could be called when the animation ends or is canceled. Therefore, we need + // to check the state of fullscreen magnification for the following actions. Border + // cleanup should only happens after a removal process. + mHandler.post(() -> { + if (getState() == DISABLING) { + cleanUpBorder(); + } + }); }}); return valueAnimator; } @@ -250,6 +265,8 @@ public class FullscreenMagnificationController implements ComponentCallbacks { // If there is an ongoing disable process or it is already disabled, return return; } + // The state should be updated as early as possible so others could check + // the ongoing process. setState(DISABLING); mShowHideBorderAnimator = createHideTargetAnimator(mFullscreenBorder); mShowHideBorderAnimator.start(); @@ -297,10 +314,13 @@ public class FullscreenMagnificationController implements ComponentCallbacks { // If there is an ongoing enable process or it is already enabled, return return; } + // The state should be updated as early as possible so others could check + // the ongoing process. + setState(ENABLING); + if (mShowHideBorderAnimator != null) { mShowHideBorderAnimator.cancel(); } - setState(ENABLING); onConfigurationChanged(mContext.getResources().getConfiguration()); mContext.registerComponentCallbacks(this); diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 6635d8b06a5d..a061d38d0c18 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -215,7 +215,8 @@ constructor( override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = refreshingFlow( - initialValue = false, + initialValue = + lockPatternUtils.isAutoPinConfirmEnabled(userRepository.getSelectedUserInfo().id), getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, ) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt index c05dcd5cea83..c59c6816a350 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt @@ -41,13 +41,12 @@ fun BouncerContainer( Box { Canvas(Modifier.fillMaxSize()) { drawRect(color = backgroundColor) } - // Separate the bouncer content into a reusable composable that - // doesn't have any SceneScope - // dependencies + // Separate the bouncer content into a reusable composable that doesn't have any + // ContentScope dependencies BouncerContent( bouncerViewModel, dialogFactory, - Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize() + Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize(), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt new file mode 100644 index 000000000000..c7b7050340a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 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.communal + +import android.annotation.SuppressLint +import android.app.DreamManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +@SysUISingleton +class DevicePosturingCommandListener +@Inject +constructor(private val commandRegistry: CommandRegistry, private val dreamManager: DreamManager) : + CoreStartable { + private val command = DevicePosturingCommand() + + override fun start() { + commandRegistry.registerCommand(COMMAND_ROOT) { command } + } + + internal inner class DevicePosturingCommand : Command { + @SuppressLint("MissingPermission") + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + when (arg.lowercase()) { + "true" -> dreamManager.setDevicePostured(true) + "false" -> dreamManager.setDevicePostured(false) + else -> { + pw.println("Invalid argument!") + help(pw) + } + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar device-postured <true|false>") + } + } + + private companion object { + const val COMMAND_ROOT = "device-postured" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt index 2d19b026489c..e3443227685f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt @@ -22,6 +22,7 @@ import com.android.systemui.communal.CommunalDreamStartable import com.android.systemui.communal.CommunalMetricsStartable import com.android.systemui.communal.CommunalOngoingContentStartable import com.android.systemui.communal.CommunalSceneStartable +import com.android.systemui.communal.DevicePosturingCommandListener import com.android.systemui.communal.log.CommunalLoggerStartable import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable import com.android.systemui.dagger.qualifiers.PerUser @@ -67,4 +68,9 @@ interface CommunalStartableModule { @IntoMap @ClassKey(CommunalMetricsStartable::class) fun bindCommunalMetricsStartable(impl: CommunalMetricsStartable): CoreStartable + + @Binds + @IntoMap + @ClassKey(DevicePosturingCommandListener::class) + fun bindDevicePosturingCommandListener(impl: DevicePosturingCommandListener): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index 7d684cab39f7..5e3b2ae0b59f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -132,8 +132,6 @@ constructor( DeviceEntryRestrictionReason.UnattendedUpdate authFlags.isPrimaryAuthRequiredAfterTimeout -> DeviceEntryRestrictionReason.SecurityTimeout - authFlags.isPrimaryAuthRequiredAfterLockout -> - DeviceEntryRestrictionReason.BouncerLockedOut isFingerprintLockedOut -> DeviceEntryRestrictionReason.StrongBiometricsLockedOut isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() -> @@ -376,8 +374,7 @@ constructor( private val interactor: DeviceUnlockedInteractor, ) : CoreStartable { override fun start() { - if (!SceneContainerFlag.isEnabled) - return + if (!SceneContainerFlag.isEnabled) return applicationScope.launch { interactor.activate() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS index 208a17c0a220..ebe603b7428c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS @@ -2,6 +2,8 @@ set noparent # Bug component: 78010 +include /services/core/java/com/android/server/biometrics/OWNERS + amiko@google.com beverlyt@google.com bhinegardner@google.com diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index a74384f61469..58692746d1e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard import android.app.IActivityTaskManager +import android.os.RemoteException import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationTarget @@ -174,25 +175,24 @@ constructor( if (!isKeyguardGoingAway) { // Since WM triggered this, we're likely not transitioning to GONE yet. See if we can // start that transition. - val startedDismiss = - keyguardDismissTransitionInteractor.startDismissKeyguardTransition( - reason = "Going away remote animation started" - ) - - if (!startedDismiss) { - // If the transition wasn't started, we're already GONE. This can happen with timing - // issues, where the remote animation took a long time to start, and something else - // caused us to unlock in the meantime. Since we're already GONE, simply end the - // remote animatiom immediately. - Log.d( - TAG, - "onKeyguardGoingAwayRemoteAnimationStart: " + - "Dismiss transition was not started; we're already GONE. " + - "Ending remote animation.", - ) - finishedCallback.onAnimationFinished() - return - } + keyguardDismissTransitionInteractor.startDismissKeyguardTransition( + reason = "Going away remote animation started", + onAlreadyGone = { + // Called if we're already GONE by the time the dismiss transition would have + // started. This can happen due to timing issues, where the remote animation + // took a long time to start, and something else caused us to unlock in the + // meantime. Since we're already GONE, simply end the remote animation + // immediately. + Log.d( + TAG, + "onKeyguardGoingAwayRemoteAnimationStart: " + + "Dismiss transition was not started; we're already GONE. " + + "Ending remote animation.", + ) + finishedCallback.onAnimationFinished() + isKeyguardGoingAway = false + }, + ) isKeyguardGoingAway = true } @@ -266,7 +266,11 @@ constructor( if (enableNewKeyguardShellTransitions) { startKeyguardTransition(lockscreenShowing, aodVisible) } else { - activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible) + try { + activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible) + } catch (e: RemoteException) { + Log.e(TAG, "Remote exception", e) + } } this.isLockscreenShowing = lockscreenShowing this.isAodVisible = aodVisible diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt index cc070b66917b..d3e2560d6a21 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/PrimaryBouncerTransitionModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.dagger import android.content.res.Resources +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.transitions.BlurConfig @@ -34,7 +35,6 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToOccludedTransitionViewModel import com.android.systemui.res.R -import com.android.systemui.window.flag.WindowBlurFlag import dagger.Binds import dagger.Module import dagger.Provides @@ -56,7 +56,7 @@ interface PrimaryBouncerTransitionModule { fun provideBlurConfig(@Main resources: Resources): BlurConfig { val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius) val maxBlurRadius = - if (WindowBlurFlag.isEnabled) { + if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) { resources.getDimensionPixelSize(R.dimen.max_shade_window_blur_radius) } else { resources.getDimensionPixelSize(R.dimen.max_window_blur_radius) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index 1b8baf657948..f11ebee46659 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -26,8 +26,8 @@ import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import android.service.notification.ZenModeConfig import android.util.Log -import com.android.settingslib.notification.modes.EnableZenModeDialog -import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger +import com.android.settingslib.notification.modes.EnableDndDialogFactory +import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -60,8 +60,7 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @SysUISingleton -class DoNotDisturbQuickAffordanceConfig -constructor( +class DoNotDisturbQuickAffordanceConfig( private val context: Context, private val controller: ZenModeController, private val interactor: ZenModeInteractor, @@ -70,7 +69,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, @Background private val backgroundScope: CoroutineScope, private val testConditionId: Uri?, - testDialog: EnableZenModeDialog?, + testDialogFactory: EnableDndDialogFactory?, ) : KeyguardQuickAffordanceConfig { @Inject @@ -118,13 +117,13 @@ constructor( ) .id - private val dialog: EnableZenModeDialog by lazy { - testDialog - ?: EnableZenModeDialog( + private val dialogFactory: EnableDndDialogFactory by lazy { + testDialogFactory + ?: EnableDndDialogFactory( context, R.style.Theme_SystemUI_Dialog, true, /* cancelIsNeutral */ - ZenModeDialogMetricsLogger(context), + EnableDndDialogMetricsLogger(context), ) } @@ -224,7 +223,7 @@ constructor( if (interactor.shouldAskForZenDuration(dnd)) { // NOTE: The dialog handles turning on the mode itself. return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( - dialog.createDialog(), + dialogFactory.createDialog(), expandable, ) } else { @@ -243,7 +242,7 @@ constructor( settingsValue == ZEN_DURATION_PROMPT -> KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( - dialog.createDialog(), + dialogFactory.createDialog(), expandable, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt index 089e5dc42df3..c0a486c005ab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt @@ -16,23 +16,31 @@ package com.android.systemui.keyguard.domain.interactor +import android.animation.ValueAnimator import android.util.Log import com.android.systemui.Flags.transitionRaceCondition import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.scene.shared.flag.SceneContainerFlag import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @SysUISingleton class KeyguardDismissTransitionInteractor @Inject constructor( + @Background private val scope: CoroutineScope, private val repository: KeyguardTransitionRepository, private val fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor, private val fromPrimaryBouncerTransitionInteractor: FromPrimaryBouncerTransitionInteractor, @@ -43,45 +51,63 @@ constructor( ) { /** - * Called to start a transition that will ultimately dismiss the keyguard from the current - * state. + * Launches a coroutine to start a transition that will ultimately dismiss the keyguard from the + * current state. * * This is called exclusively by sources that can authoritatively say we should be unlocked, * including KeyguardSecurityContainerController and WindowManager. * - * Returns [false] if the transition was not started, because we're already GONE or we don't - * know how to dismiss keyguard from the current state. + * This is one of the few transitions that is started outside of the From*TransitionInteractor + * classes. This is because this is an external call that must be respected, so it doesn't + * matter what state we're in/coming from - we must transition from that state to GONE. + * + * Invokes [onAlreadyGone] if the transition was not started because we're already GONE by the + * time the coroutine runs. */ - fun startDismissKeyguardTransition(reason: String = ""): Boolean { - if (SceneContainerFlag.isEnabled) return false + @JvmOverloads + fun startDismissKeyguardTransition(reason: String = "", onAlreadyGone: (() -> Unit)? = null) { + if (SceneContainerFlag.isEnabled) return Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)") - val startedState = - if (transitionRaceCondition()) { - repository.currentTransitionInfo.to + + scope.launch { + val startedState = + if (transitionRaceCondition()) { + repository.currentTransitionInfo.to + } else { + repository.currentTransitionInfoInternal.value.to + } + + val animator: ValueAnimator? = + when (startedState) { + LOCKSCREEN -> fromLockscreenTransitionInteractor + PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor + ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor + AOD -> fromAodTransitionInteractor + DOZING -> fromDozingTransitionInteractor + OCCLUDED -> fromOccludedTransitionInteractor + else -> null + }?.getDefaultAnimatorForTransitionsToState(KeyguardState.GONE) + + if (startedState != KeyguardState.GONE && animator != null) { + repository.startTransition( + TransitionInfo( + "KeyguardDismissTransitionInteractor" + + if (reason.isNotBlank()) "($reason)" else "", + startedState, + KeyguardState.GONE, + animator, + TransitionModeOnCanceled.LAST_VALUE, + ) + ) } else { - repository.currentTransitionInfoInternal.value.to - } - when (startedState) { - LOCKSCREEN -> fromLockscreenTransitionInteractor.dismissKeyguard() - PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.dismissPrimaryBouncer() - ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.dismissAlternateBouncer() - AOD -> fromAodTransitionInteractor.dismissAod() - DOZING -> fromDozingTransitionInteractor.dismissFromDozing() - KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.dismissFromOccluded() - KeyguardState.GONE -> { Log.i( TAG, - "Already transitioning to GONE; ignoring startDismissKeyguardTransition.", + "Can't transition to GONE from $startedState; " + + "ignoring startDismissKeyguardTransition.", ) - return false - } - else -> { - Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.") - return false + onAlreadyGone?.invoke() } } - - return true } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 213083db71c9..9c886b228ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -31,6 +31,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.flow.combine object KeyguardSmartspaceViewBinder { @JvmStatic @@ -43,21 +44,25 @@ object KeyguardSmartspaceViewBinder { return keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") { - clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay - -> - updateDateWeatherToBurnInLayer( - keyguardRootView, - clockViewModel, - smartspaceViewModel, + combine( + smartspaceViewModel.isWeatherVisible, + clockViewModel.hasCustomWeatherDataDisplay, + ::Pair, ) - blueprintInteractor.refreshBlueprint( - Config( - Type.SmartspaceVisibility, - checkPriority = false, - terminatePrevious = false, + .collect { + updateDateWeatherToBurnInLayer( + keyguardRootView, + clockViewModel, + smartspaceViewModel, ) - ) - } + blueprintInteractor.refreshBlueprint( + Config( + Type.SmartspaceVisibility, + checkPriority = false, + terminatePrevious = false, + ) + ) + } } launch("$TAG#smartspaceViewModel.bcSmartspaceVisibility") { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index cd038d799f42..9319bc890c6c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -23,6 +23,8 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.GONE +import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController @@ -195,24 +197,13 @@ constructor( smartspaceController.requestSmartspaceUpdate() constraintSet.apply { - val weatherVisibility = - when (keyguardSmartspaceViewModel.isWeatherVisible.value) { - true -> ConstraintSet.VISIBLE - false -> ConstraintSet.GONE - } - setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility) - setAlpha( - sharedR.id.weather_smartspace_view, - if (weatherVisibility == View.VISIBLE) 1f else 0f, - ) - val dateVisibility = - if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE - else ConstraintSet.VISIBLE - setVisibility(sharedR.id.date_smartspace_view, dateVisibility) - setAlpha( - sharedR.id.date_smartspace_view, - if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f, - ) + val showWeather = keyguardSmartspaceViewModel.isWeatherVisible.value + setVisibility(sharedR.id.weather_smartspace_view, if (showWeather) VISIBLE else GONE) + setAlpha(sharedR.id.weather_smartspace_view, if (showWeather) 1f else 0f) + + val showDateView = !keyguardClockViewModel.hasCustomWeatherDataDisplay.value + setVisibility(sharedR.id.date_smartspace_view, if (showDateView) VISIBLE else GONE) + setAlpha(sharedR.id.date_smartspace_view, if (showDateView) 1f else 0f) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index 92bb5e6029cb..733d7d71061e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION -import com.android.systemui.window.flag.WindowBlurFlag import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -73,7 +72,7 @@ constructor( onStep = alphaForAnimationStep, ) - val lockscreenAlpha: Flow<Float> = if (WindowBlurFlag.isEnabled) alphaFlow else emptyFlow() + val lockscreenAlpha: Flow<Float> = if (Flags.bouncerUiRevamp()) alphaFlow else emptyFlow() val notificationAlpha: Flow<Float> = if (Flags.bouncerUiRevamp()) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt index e3b55874de6f..26bf0bc258e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow /** * Breaks down AOD->PRIMARY BOUNCER transition into discrete steps for corresponding views to @@ -54,6 +56,12 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl override val windowBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + val lockscreenAlpha: Flow<Float> = + if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f) + else emptyFlow() + + val notificationAlpha = lockscreenAlpha + override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0.0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt index c937d5c6453d..d9ca267f9445 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION import com.android.systemui.keyguard.shared.model.Edge @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow /** * Breaks down DOZING->PRIMARY BOUNCER transition into discrete steps for corresponding views to @@ -64,6 +66,13 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio }, onFinish = { blurConfig.maxBlurRadiusPx }, ) + + val lockscreenAlpha: Flow<Float> = + if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f) + else emptyFlow() + + val notificationAlpha: Flow<Float> = lockscreenAlpha + override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index eaba5d5a149c..e51e05b8ab61 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -96,9 +96,12 @@ constructor( private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, + private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel, private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, + private val dozingToPrimaryBouncerTransitionViewModel: + DozingToPrimaryBouncerTransitionViewModel, private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel, private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel, private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, @@ -243,9 +246,11 @@ constructor( aodToGoneTransitionViewModel.lockscreenAlpha(viewState), aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), + aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha, dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), + dozingToPrimaryBouncerTransitionViewModel.lockscreenAlpha, dreamingToAodTransitionViewModel.lockscreenAlpha, dreamingToGoneTransitionViewModel.lockscreenAlpha, dreamingToLockscreenTransitionViewModel.lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index 5ee80a7b7442..f8425c16c341 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -77,7 +77,7 @@ constructor( isWeatherVisible( clockIncludesCustomWeatherDisplay = keyguardClockViewModel.hasCustomWeatherDataDisplay.value, - isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value, + isWeatherEnabled = isWeatherEnabled.value, ), ) diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index 6351d7d28d07..c9d6f81dc79c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -18,6 +18,7 @@ package com.android.systemui.log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBuffer.Companion.DEFAULT_LOGBUFFER_TRACK_NAME import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize import com.android.systemui.log.echo.LogcatEchoTrackerAlways import javax.inject.Inject @@ -27,7 +28,7 @@ class LogBufferFactory @Inject constructor( private val dumpManager: DumpManager, - private val logcatEchoTracker: LogcatEchoTracker + private val logcatEchoTracker: LogcatEchoTracker, ) { @JvmOverloads fun create( @@ -35,9 +36,11 @@ constructor( maxSize: Int, systrace: Boolean = true, alwaysLogToLogcat: Boolean = false, + systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME, ): LogBuffer { val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker - val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace) + val buffer = + LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace, systraceTrackName) dumpManager.registerBuffer(name, buffer) return buffer } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt index a6b9442b1270..71c8d1f5b4c7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt @@ -130,7 +130,7 @@ constructor( null, // no action to perform when clicked context.getString(R.string.controls_media_button_connecting), if (Flags.mediaControlsUiUpdate()) { - context.getDrawable(R.drawable.ic_media_connecting_status_container) + context.getDrawable(R.drawable.ic_media_connecting_button_container) } else { context.getDrawable(R.drawable.ic_media_connecting_container) }, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt index 9bf556cf07c2..5fef81f4596a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt @@ -71,7 +71,7 @@ fun createActionsFromState( null, // no action to perform when clicked context.getString(R.string.controls_media_button_connecting), if (Flags.mediaControlsUiUpdate()) { - context.getDrawable(R.drawable.ic_media_connecting_status_container) + context.getDrawable(R.drawable.ic_media_connecting_button_container) } else { context.getDrawable(R.drawable.ic_media_connecting_container) }, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 3f14b55e46a1..9270fff61c43 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -236,11 +236,29 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void onDisplayReady(int displayId) { CommandQueue.Callbacks.super.onDisplayReady(displayId); + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onDisplayReady(displayId); + } catch (RemoteException e) { + Log.e(TAG, "onDisplayReady() failed", e); + } } @Override public void onDisplayRemoved(int displayId) { CommandQueue.Callbacks.super.onDisplayRemoved(displayId); + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onDisplayRemoved(displayId); + } catch (RemoteException e) { + Log.e(TAG, "onDisplayRemoved() failed", e); + } } // Separated into a method to keep setDependencies() clean/readable. diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 65fba28c5465..b53685ee3cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -88,11 +88,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transitions @@ -580,7 +580,7 @@ constructor( } @Composable - private fun SceneScope.QuickQuickSettingsElement() { + private fun ContentScope.QuickQuickSettingsElement() { val qqsPadding = viewModel.qqsHeaderHeight val bottomPadding = viewModel.qqsBottomPadding DisposableEffect(Unit) { @@ -664,7 +664,7 @@ constructor( } @Composable - private fun SceneScope.QuickSettingsElement() { + private fun ContentScope.QuickSettingsElement() { val qqsPadding = viewModel.qqsHeaderHeight val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) Column( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index a22eb3a8d517..185ea93387a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel @@ -27,7 +27,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec /** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { - @Composable fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) + @Composable fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) @Composable fun EditTileGrid( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 39408d3dee72..c72381f45239 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.development.ui.compose.BuildNumber @@ -63,7 +63,7 @@ constructor( @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, ) : GridLayout by delegateGridLayout { @Composable - override fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { val viewModel = rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") { viewModelFactory.create() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index 8fda23d54625..5cb30b999e13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.composefragment.ui.GridAnchor @@ -38,7 +38,7 @@ import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey import com.android.systemui.res.R @Composable -fun SceneScope.QuickQuickSettings( +fun ContentScope.QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 6c1906bb906f..bcc44d397eb2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -20,11 +20,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun SceneScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { +fun ContentScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 66961b6efe46..4432d336237f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.ContentScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider @@ -58,7 +58,7 @@ constructor( ) : PaginatableGridLayout { @Composable - override fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { DisposableEffect(tiles) { val token = Any() tiles.forEach { it.startListening(token) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 04f0b8736598..1e8ef359bb71 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -42,7 +42,7 @@ import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.notification.modes.EnableZenModeDialog; +import com.android.settingslib.notification.modes.EnableDndDialogFactory; import com.android.systemui.Prefs; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; @@ -59,7 +59,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger; +import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; @@ -84,7 +84,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final SharedPreferences mSharedPreferences; private final UserSettingObserver mSettingZenDuration; private final DialogTransitionAnimator mDialogTransitionAnimator; - private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger; + private final QSEnableDndDialogMetricsLogger mQSDndDurationDialogLogger; private boolean mListening; @@ -121,7 +121,7 @@ public class DndTile extends QSTileImpl<BooleanState> { refreshState(); } }; - mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext); + mQSDndDurationDialogLogger = new QSEnableDndDialogMetricsLogger(mContext); } public static void setVisible(Context context, boolean visible) { @@ -201,9 +201,9 @@ public class DndTile extends QSTileImpl<BooleanState> { } private Dialog makeZenModeDialog() { - AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog, + AlertDialog dialog = new EnableDndDialogFactory(mContext, R.style.Theme_SystemUI_Dialog, true /* cancelIsNeutral */, - mQSZenDialogMetricsLogger).createDialog(); + mQSDndDurationDialogLogger).createDialog(); SystemUIDialog.applyFlags(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); SystemUIDialog.registerDismissListener(dialog); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index e93cec875429..42b35c736d42 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -191,8 +191,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> @Override public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) { - handleClick(() -> - callback.accept(new ScreenRecordDetailsViewModel()) + handleClick(() -> executeWhenUnlockedKeyguard( + () -> callback.accept(new ScreenRecordDetailsViewModel(mController, + this::onStartRecordingClicked))) ); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java index 340cb68a83a4..6b5a22a4fc09 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java @@ -1506,15 +1506,17 @@ public class InternetDetailsContentController implements AccessPointController.A Intent getConfiguratorQrCodeGeneratorIntentOrNull(WifiEntry wifiEntry) { if (!mFeatureFlags.isEnabled(Flags.SHARE_WIFI_QS_BUTTON) || wifiEntry == null - || mWifiManager == null || !wifiEntry.canShare() - || wifiEntry.getWifiConfiguration() == null) { + || mWifiManager == null || !wifiEntry.canShare()) { + return null; + } + var wifiConfiguration = wifiEntry.getWifiConfiguration(); + if (wifiConfiguration == null) { return null; } Intent intent = new Intent(); intent.setAction(WifiDppIntentHelper.ACTION_CONFIGURATOR_AUTH_QR_CODE_GENERATOR); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - WifiDppIntentHelper.setConfiguratorIntentExtra(intent, mWifiManager, - wifiEntry.getWifiConfiguration()); + WifiDppIntentHelper.setConfiguratorIntentExtra(intent, mWifiManager, wifiConfiguration); return intent; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java index b3f66a6bf9dd..5196a22df29a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java @@ -19,7 +19,7 @@ package com.android.systemui.qs.tiles.dialog; import android.content.Context; import com.android.internal.logging.UiEventLogger; -import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger; +import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger; import com.android.systemui.qs.QSDndEvent; import com.android.systemui.qs.QSEvents; @@ -30,10 +30,10 @@ import com.android.systemui.qs.QSEvents; * * Other names for DND (Do Not Disturb) include "Zen" and "Priority only". */ -public class QSZenModeDialogMetricsLogger extends ZenModeDialogMetricsLogger { +public class QSEnableDndDialogMetricsLogger extends EnableDndDialogMetricsLogger { private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); - public QSZenModeDialogMetricsLogger(Context context) { + public QSEnableDndDialogMetricsLogger(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt index 42cb1248ccff..54e4a521c239 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt @@ -17,27 +17,45 @@ package com.android.systemui.qs.tiles.dialog import android.view.LayoutInflater +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController +import com.android.systemui.screenrecord.ScreenRecordPermissionViewBinder /** The view model used for the screen record details view in the Quick Settings */ -class ScreenRecordDetailsViewModel() : TileDetailsViewModel() { +class ScreenRecordDetailsViewModel( + private val recordingController: RecordingController, + private val onStartRecordingClicked: Runnable, +) : TileDetailsViewModel() { + + private var viewBinder: ScreenRecordPermissionViewBinder = + recordingController.createScreenRecordPermissionViewBinder(onStartRecordingClicked) + @Composable override fun GetContentView() { // TODO(b/378514312): Finish implementing this function. + + if (recordingController.isScreenCaptureDisabled) { + // TODO(b/388345506): Show disabled page here. + return + } + AndroidView( - modifier = Modifier.fillMaxWidth().heightIn(max = VIEW_MAX_HEIGHT), + modifier = Modifier.fillMaxWidth().fillMaxHeight(), factory = { context -> // Inflate with the existing dialog xml layout - LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null) + val view = LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null) + viewBinder.bind(view) + + view + // TODO(b/378514473): Revamp the details view according to the spec. }, + onRelease = { viewBinder.unbind() }, ) } @@ -54,8 +72,4 @@ class ScreenRecordDetailsViewModel() : TileDetailsViewModel() { // No sub-title in this tile. return "" } - - companion object { - private val VIEW_MAX_HEIGHT: Dp = 320.dp - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt index 594394f68d48..5ce7f0d039c8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger import javax.inject.Inject @SysUISingleton @@ -39,6 +40,7 @@ constructor( // TODO(b/353896370): The domain layer should not have to depend on the UI layer. private val dialogDelegate: ModesDialogDelegate, private val zenModeInteractor: ZenModeInteractor, + private val dialogEventLogger: ModesDialogEventLogger, ) : QSTileUserActionInteractor<ModesTileModel> { val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) @@ -78,7 +80,16 @@ constructor( Log.wtf(TAG, "Triggered DND but it's null!?") return } - zenModeInteractor.activateMode(dnd) + + if (zenModeInteractor.shouldAskForZenDuration(dnd)) { + dialogEventLogger.logOpenDurationDialog(dnd) + // NOTE: The dialog handles turning on the mode itself. + val dialog = dialogDelegate.makeDndDurationDialog() + dialog.show() + } else { + dialogEventLogger.logModeOn(dnd) + zenModeInteractor.activateMode(dnd) + } } else { zenModeInteractor.deactivateAllModes() } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index e3cf41191384..adf9eb44e162 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -58,6 +58,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.PatternMatcher; import android.os.Process; @@ -146,7 +147,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public static final String TAG_OPS = "OverviewProxyService"; private static final long BACKOFF_MILLIS = 1000; private static final long DEFERRED_CALLBACK_MILLIS = 5000; - // Max backoff caps at 5 mins private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; @@ -183,6 +183,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private int mConnectionBackoffAttempts; private boolean mBound; private boolean mIsEnabled; + // This is set to false when the overview service is requested to be bound until it is notified + // that the previous service has been cleaned up in IOverviewProxy#onUnbind(). It is also set to + // true after a 1000ms timeout by mDeferredBindAfterTimedOutCleanup. + private boolean mIsPrevServiceCleanedUp = true; private boolean mIsSystemOrVisibleBgUser; private int mCurrentBoundedUserId = -1; @@ -489,6 +493,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis retryConnectionWithBackoff(); }; + private final Runnable mDeferredBindAfterTimedOutCleanup = () -> { + Log.w(TAG_OPS, "Timed out waiting for previous service to clean up, binding to new one"); + mIsPrevServiceCleanedUp = true; + maybeBindService(); + }; + private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -859,6 +869,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mShadeViewControllerLazy.get().cancelInputFocusTransfer(); }); } + mIsPrevServiceCleanedUp = true; startConnectionToCurrentUser(); } @@ -889,6 +900,19 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } mHandler.removeCallbacks(mConnectionRunnable); + maybeBindService(); + } + + private void maybeBindService() { + if (!mIsPrevServiceCleanedUp) { + Log.w(TAG_OPS, "Skipping connection to TouchInteractionService until previous" + + " instance is cleaned up."); + if (!mHandler.hasCallbacks(mDeferredConnectionCallback)) { + mHandler.postDelayed(mDeferredBindAfterTimedOutCleanup, BACKOFF_MILLIS); + } + return; + } + // Avoid creating TouchInteractionService because the System user in HSUM mode does not // interact with UI elements UserHandle currentUser = UserHandle.of(mUserTracker.getUserId()); @@ -907,6 +931,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.e(TAG_OPS, "Unable to bind because of security error", e); } if (mBound) { + mIsPrevServiceCleanedUp = false; // Ensure that connection has been established even if it thinks it is bound mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS); } else { @@ -960,6 +985,24 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Always unbind the service (ie. if called through onNullBinding or onBindingDied) mContext.unbindService(mOverviewServiceConnection); mBound = false; + if (mOverviewProxy != null) { + try { + mOverviewProxy.onUnbind(new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + // Received Launcher reply, try to bind anew. + mIsPrevServiceCleanedUp = true; + if (mHandler.hasCallbacks(mDeferredBindAfterTimedOutCleanup)) { + mHandler.removeCallbacks(mDeferredBindAfterTimedOutCleanup); + maybeBindService(); + } + } + }); + } catch (RemoteException e) { + Log.w(TAG_OPS, "disconnectFromLauncherService failed to notify Launcher"); + mIsPrevServiceCleanedUp = true; + } + } } if (mOverviewProxy != null) { @@ -1189,6 +1232,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis pw.print(" mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis); pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion); pw.print(" mNavBarMode="); pw.println(mNavBarMode); + pw.print(" mIsPrevServiceCleanedUp="); pw.println(mIsPrevServiceCleanedUp); mSysUiState.dump(pw, args); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 9ee99e45ceeb..140fbf340f8b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -70,6 +70,8 @@ public class RecordingController private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; private final ScreenRecordPermissionDialogDelegate.Factory mScreenRecordPermissionDialogDelegateFactory; + private final ScreenRecordPermissionViewBinder.Factory + mScreenRecordPermissionViewBinderFactory; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -118,7 +120,8 @@ public class RecordingController MediaProjectionMetricsLogger mediaProjectionMetricsLogger, ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate, ScreenRecordPermissionDialogDelegate.Factory - screenRecordPermissionDialogDelegateFactory) { + screenRecordPermissionDialogDelegateFactory, + ScreenRecordPermissionViewBinder.Factory screenRecordPermissionViewBinderFactory) { mMainExecutor = mainExecutor; mDevicePolicyResolver = devicePolicyResolver; mBroadcastDispatcher = broadcastDispatcher; @@ -127,6 +130,7 @@ public class RecordingController mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory; + mScreenRecordPermissionViewBinderFactory = screenRecordPermissionViewBinderFactory; BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); @@ -151,8 +155,7 @@ public class RecordingController * If screen capturing is currently not allowed it will return a dialog * that warns users about it. */ public Dialog createScreenRecordDialog(@Nullable Runnable onStartRecordingClicked) { - if (mDevicePolicyResolver.get() - .isScreenCaptureCompletelyDisabled(getHostUserHandle())) { + if (isScreenCaptureDisabled()) { return mScreenCaptureDisabledDialogDelegate.createSysUIDialog(); } @@ -165,6 +168,27 @@ public class RecordingController } /** + * Create a view binder that controls the logic of views inside the screen record permission + * view. + * @param onStartRecordingClicked the callback that is run when the start button is clicked. + */ + public ScreenRecordPermissionViewBinder createScreenRecordPermissionViewBinder( + @Nullable Runnable onStartRecordingClicked + ) { + return mScreenRecordPermissionViewBinderFactory + .create(getHostUserHandle(), getHostUid(), this, + onStartRecordingClicked); + } + + /** + * Check if screen capture is currently disabled for this device and user. + */ + public boolean isScreenCaptureDisabled() { + return mDevicePolicyResolver.get() + .isScreenCaptureCompletelyDisabled(getHostUserHandle()); + } + + /** * Start counting down in preparation to start a recording * @param ms Total time in ms to wait before starting * @param interval Time in ms per countdown step diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt index 9fcb3dfc0ad3..23df1c5441bf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt @@ -49,6 +49,9 @@ import com.android.systemui.mediaprojection.permission.ScreenShareOption import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject class ScreenRecordPermissionViewBinder( private val hostUserHandle: UserHandle, @@ -68,6 +71,38 @@ class ScreenRecordPermissionViewBinder( mediaProjectionMetricsLogger, defaultSelectedMode, ) { + @AssistedInject + constructor( + @Assisted hostUserHandle: UserHandle, + @Assisted hostUid: Int, + mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, + displayManager: DisplayManager, + @Assisted controller: RecordingController, + activityStarter: ActivityStarter, + userContextProvider: UserContextProvider, + @Assisted onStartRecordingClicked: Runnable?, + ) : this( + hostUserHandle, + hostUid, + mediaProjectionMetricsLogger, + defaultSelectedMode = SINGLE_APP, + displayManager, + controller, + activityStarter, + userContextProvider, + onStartRecordingClicked, + ) + + @AssistedFactory + interface Factory { + fun create( + hostUserHandle: UserHandle, + hostUid: Int, + recordingController: RecordingController, + onStartRecordingClicked: Runnable?, + ): ScreenRecordPermissionViewBinder + } + private lateinit var tapsSwitch: Switch private lateinit var audioSwitch: Switch private lateinit var tapsView: View diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index 0650f8606ba9..9a1ffcbab8d1 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -35,7 +35,6 @@ import android.view.animation.DecelerateInterpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.window.flag.WindowBlurFlag; /** * Drawable used on SysUI scrims. @@ -214,10 +213,6 @@ public class ScrimDrawable extends Drawable { public void draw(@NonNull Canvas canvas) { mPaint.setColor(mMainColor); mPaint.setAlpha(mAlpha); - if (WindowBlurFlag.isEnabled()) { - // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition - mPaint.setAlpha((int) (0.5f * mAlpha)); - } if (mConcaveInfo != null) { drawConcave(canvas); } else if (mCornerRadiusEnabled && mCornerRadius > 0) { diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 03a8d17847f9..49f3cfc4ceaf 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -39,10 +39,8 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.res.R; import com.android.systemui.shade.TouchLogger; import com.android.systemui.util.LargeScreenUtils; -import com.android.systemui.window.flag.WindowBlurFlag; import java.util.concurrent.Executor; @@ -252,13 +250,6 @@ public class ScrimView extends View { if (mBlendWithMainColor) { mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount); } - if (WindowBlurFlag.isEnabled()) { - int layerAbove = ColorUtils.setAlphaComponent( - getResources().getColor(R.color.shade_panel, null), - (int) (0.4f * 255)); - int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); - mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow); - } drawable.setColor(mainTinted, animated); } else { boolean hasAlpha = Color.alpha(mTintColor) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 38f7c39203f0..ca2fbdd1cdd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -34,6 +34,7 @@ import androidx.dynamicanimation.animation.SpringForce import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.TrackTracer import com.android.systemui.Dumpable +import com.android.systemui.Flags import com.android.systemui.Flags.spatialModelAppPushback import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.dagger.SysUISingleton @@ -52,8 +53,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.WallpaperController import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor -import com.android.systemui.window.flag.WindowBlurFlag import com.android.wm.shell.appzoomout.AppZoomOut + import java.io.PrintWriter import java.util.Optional import javax.inject.Inject @@ -230,7 +231,7 @@ constructor( val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius) // Make blur be 0 if it is necessary to stop blur effect. if (scrimsVisible) { - if (!WindowBlurFlag.isEnabled) { + if (!Flags.notificationShadeBlur()) { blur = 0 } } @@ -258,7 +259,9 @@ constructor( } private val shouldBlurBeOpaque: Boolean - get() = if (WindowBlurFlag.isEnabled) false else scrimsVisible && !blursDisabledForAppLaunch + get() = + if (Flags.notificationShadeBlur()) false + else scrimsVisible && !blursDisabledForAppLaunch /** Callback that updates the window blur value and is called only once per frame. */ @VisibleForTesting @@ -388,7 +391,7 @@ constructor( } private fun initBlurListeners() { - if (!WindowBlurFlag.isEnabled) return + if (!Flags.bouncerUiRevamp()) return applicationScope.launch { Log.d(TAG, "Starting coroutines for window root view blur") @@ -523,7 +526,7 @@ constructor( private fun scheduleUpdate() { val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut() zoomOutCalculatedFromShadeRadius = zoomOutFromShadeRadius - if (WindowBlurFlag.isEnabled) { + if (Flags.bouncerUiRevamp()) { updateScheduled = windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque) return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS index 72b03bfa20c3..b2764e1a2302 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS @@ -16,6 +16,12 @@ per-file *Keyguard* = set noparent per-file *Keyguard* = file:../keyguard/OWNERS # Not setting noparent here, since *Notification* also matches some status bar notification chips files (statusbar/chips/notification) which should be owned by the status bar team. per-file *Notification* = file:notification/OWNERS +# Files that control blur effects on shade +per-file *NotificationShadeDepth* = set noparent +per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com +per-file *NotificationShadeDepth* = file:../keyguard/OWNERS +per-file *Blur* = set noparent +per-file *Blur* = shanh@google.com, rahulbanerjee@google.com # Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*) per-file *Mode* = file:notification/OWNERS per-file *RemoteInput* = set noparent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index 86954d569199..108d737e7658 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -30,7 +30,9 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel +import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel @@ -76,14 +78,24 @@ constructor( OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon) } + val colors = + if (StatusBarNotifChips.isEnabled && state.promotedContent != null) { + state.promotedContent.toCustomColorsModel() + } else { + ColorsModel.Themed + } + // This block mimics OngoingCallController#updateChip. if (state.startTimeMs <= 0L) { // If the start time is invalid, don't show a timer and show just an // icon. See b/192379214. OngoingActivityChipModel.Shown.IconOnly( icon = icon, - colors = ColorsModel.Themed, - getOnClickListener(state), + colors = colors, + onClickListenerLegacy = getOnClickListener(state), + // TODO(b/372657935): Add click support for the call chip when + // StatusBarChipModernization is enabled. + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) } else { val startTimeInElapsedRealtime = @@ -91,9 +103,12 @@ constructor( systemClock.elapsedRealtime() OngoingActivityChipModel.Shown.Timer( icon = icon, - colors = ColorsModel.Themed, + colors = colors, startTimeMs = startTimeInElapsedRealtime, - getOnClickListener(state), + onClickListenerLegacy = getOnClickListener(state), + // TODO(b/372657935): Add click support for the call chip when + // StatusBarChipModernization is enabled. + clickBehavior = OngoingActivityChipModel.ClickBehavior.None, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt index 3422337523f9..baa8eec5f767 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -204,13 +205,25 @@ constructor( colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener( - createCastScreenToOtherDeviceDialogDelegate(state), - dialogTransitionAnimator, - DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"), - logger, - TAG, - ), + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createCastScreenToOtherDeviceDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + onClick = + createDialogLaunchOnClickCallback( + createCastScreenToOtherDeviceDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ) + ), ) } @@ -225,16 +238,24 @@ constructor( ) ), colors = ColorsModel.Red, - createDialogLaunchOnClickListener( - createGenericCastToOtherDeviceDialogDelegate(deviceName), - dialogTransitionAnimator, - DialogCuj( - Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, - tag = "Cast to other device audio only", + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createGenericCastToOtherDeviceDialogDelegate(deviceName), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + createDialogLaunchOnClickCallback( + createGenericCastToOtherDeviceDialogDelegate(deviceName), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ) ), - logger, - TAG, - ), ) } @@ -256,6 +277,13 @@ constructor( companion object { @DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected + private val DIALOG_CUJ = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device") + private val DIALOG_CUJ_AUDIO_ONLY = + DialogCuj( + Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, + tag = "Cast to other device audio only", + ) private val TAG = "CastToOtherVM".pad() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt index 2121f94caced..4fad01d9f448 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor.Companion.isOngoingCallNotification import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map /** An interactor for the notification chips shown in the status bar. */ @SysUISingleton @@ -88,45 +90,56 @@ constructor( private val promotedNotificationInteractors = MutableStateFlow<List<SingleNotificationChipInteractor>>(emptyList()) + /** + * The notifications that are promoted and ongoing. + * + * Explicitly does *not* include any ongoing call notifications, even if the call notifications + * meet the promotion criteria. Those call notifications will be handled by + * [com.android.systemui.statusbar.chips.call.domain.CallChipInteractor] instead. See + * b/388521980. + */ + private val promotedOngoingNotifications = + activeNotificationsInteractor.promotedOngoingNotifications.map { notifs -> + notifs.filterNot { it.isOngoingCallNotification() } + } + override fun start() { if (!StatusBarNotifChips.isEnabled) { return } backgroundScope.launch("StatusBarNotificationChipsInteractor") { - activeNotificationsInteractor.promotedOngoingNotifications - .pairwise(initialValue = emptyList()) - .collect { (oldNotifs, currentNotifs) -> - val removedNotifKeys = - oldNotifs.map { it.key }.minus(currentNotifs.map { it.key }.toSet()) - removedNotifKeys.forEach { removedNotifKey -> - val wasRemoved = promotedNotificationInteractorMap.remove(removedNotifKey) - if (wasRemoved == null) { - logger.w({ - "Attempted to remove $str1 from interactor map but it wasn't present" - }) { - str1 = removedNotifKey - } + promotedOngoingNotifications.pairwise(initialValue = emptyList()).collect { + (oldNotifs, currentNotifs) -> + val removedNotifKeys = + oldNotifs.map { it.key }.minus(currentNotifs.map { it.key }.toSet()) + removedNotifKeys.forEach { removedNotifKey -> + val wasRemoved = promotedNotificationInteractorMap.remove(removedNotifKey) + if (wasRemoved == null) { + logger.w({ + "Attempted to remove $str1 from interactor map but it wasn't present" + }) { + str1 = removedNotifKey } } + } - currentNotifs.forEach { notif -> - val interactor = - promotedNotificationInteractorMap.computeIfAbsent(notif.key) { - singleNotificationChipInteractorFactory.create( - notif, - creationTime = systemClock.currentTimeMillis(), - ) - } - interactor.setNotification(notif) - } - logger.d({ "Interactors: $str1" }) { - str1 = - promotedNotificationInteractorMap.keys.joinToString(separator = " /// ") - } - promotedNotificationInteractors.value = - promotedNotificationInteractorMap.values.toList() + currentNotifs.forEach { notif -> + val interactor = + promotedNotificationInteractorMap.computeIfAbsent(notif.key) { + singleNotificationChipInteractorFactory.create( + notif, + creationTime = systemClock.currentTimeMillis(), + ) + } + interactor.setNotification(notif) } + logger.d({ "Interactors: $str1" }) { + str1 = promotedNotificationInteractorMap.keys.joinToString(separator = " /// ") + } + promotedNotificationInteractors.value = + promotedNotificationInteractorMap.values.toList() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index ec3a5b271e35..b7cad625b7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips -import com.android.systemui.statusbar.chips.ui.model.ColorsModel +import com.android.systemui.statusbar.chips.ui.model.ColorsModel.Companion.toCustomColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor @@ -72,11 +72,7 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key) } - val colors = - ColorsModel.Custom( - backgroundColorInt = this.promotedContent.colors.backgroundColor, - primaryTextColorInt = this.promotedContent.colors.primaryTextColor, - ) + val colors = this.promotedContent.toCustomColorsModel() val onClickListener = View.OnClickListener { // The notification pipeline needs everything to run on the main thread, so keep @@ -87,6 +83,7 @@ constructor( ) } } + val clickBehavior = OngoingActivityChipModel.ClickBehavior.None val isShowingHeadsUpFromChipTap = headsUpState is TopPinnedState.Pinned && @@ -95,7 +92,12 @@ constructor( if (isShowingHeadsUpFromChipTap) { // If the user tapped this chip to show the HUN, we want to just show the icon because // the HUN will show the rest of the information. - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + return OngoingActivityChipModel.Shown.IconOnly( + icon, + colors, + onClickListener, + clickBehavior, + ) } if (this.promotedContent.shortCriticalText != null) { @@ -104,6 +106,7 @@ constructor( colors, this.promotedContent.shortCriticalText, onClickListener, + clickBehavior, ) } @@ -115,11 +118,21 @@ constructor( // notification will likely just be set to the current time, which would cause the chip // to always show "now". We don't want early testers to get that experience since it's // not what will happen at launch, so just don't show any time. - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + return OngoingActivityChipModel.Shown.IconOnly( + icon, + colors, + onClickListener, + clickBehavior, + ) } if (this.promotedContent.time == null) { - return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + return OngoingActivityChipModel.Shown.IconOnly( + icon, + colors, + onClickListener, + clickBehavior, + ) } when (this.promotedContent.time.mode) { PromotedNotificationContentModel.When.Mode.BasicTime -> { @@ -128,6 +141,7 @@ constructor( colors, time = this.promotedContent.time.time, onClickListener, + clickBehavior, ) } PromotedNotificationContentModel.When.Mode.CountUp -> { @@ -136,6 +150,7 @@ constructor( colors, startTimeMs = this.promotedContent.time.time, onClickListener, + clickBehavior, ) } PromotedNotificationContentModel.When.Mode.CountDown -> { @@ -145,6 +160,7 @@ constructor( colors, startTimeMs = this.promotedContent.time.time, onClickListener, + clickBehavior, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt index 0065593c7b73..7f2327a742e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.time.SystemClock @@ -91,16 +92,24 @@ constructor( ), colors = ColorsModel.Red, startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener( - createDelegate(state.recordedTask), - dialogTransitionAnimator, - DialogCuj( - Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, - tag = "Screen record", + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createDelegate(state.recordedTask), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + createDialogLaunchOnClickCallback( + dialogDelegate = createDelegate(state.recordedTask), + dialogTransitionAnimator = dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ) ), - logger, - TAG, - ), ) } } @@ -154,6 +163,8 @@ constructor( companion object { @DrawableRes val ICON = R.drawable.ic_screenrecord + private val DIALOG_CUJ = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Screen record") private val TAG = "ScreenRecordVM".pad() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt index 2af86a51cf70..6654d4a8f104 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -128,13 +129,25 @@ constructor( colors = ColorsModel.Red, // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time. startTimeMs = systemClock.elapsedRealtime(), - createDialogLaunchOnClickListener( - createShareScreenToAppDialogDelegate(state), - dialogTransitionAnimator, - DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app"), - logger, - TAG, - ), + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createShareScreenToAppDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + onClick = + createDialogLaunchOnClickCallback( + createShareScreenToAppDialogDelegate(state), + dialogTransitionAnimator, + DIALOG_CUJ, + logger, + TAG, + ) + ), ) } @@ -150,16 +163,24 @@ constructor( ) ), colors = ColorsModel.Red, - createDialogLaunchOnClickListener( - createGenericShareToAppDialogDelegate(), - dialogTransitionAnimator, - DialogCuj( - Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, - tag = "Share to app audio only", + onClickListenerLegacy = + createDialogLaunchOnClickListener( + createGenericShareToAppDialogDelegate(), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ), + clickBehavior = + OngoingActivityChipModel.ClickBehavior.ExpandAction( + createDialogLaunchOnClickCallback( + createGenericShareToAppDialogDelegate(), + dialogTransitionAnimator, + DIALOG_CUJ_AUDIO_ONLY, + logger, + TAG, + ) ), - logger, - TAG, - ), ) } @@ -180,6 +201,10 @@ constructor( companion object { @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all + private val DIALOG_CUJ = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app") + private val DIALOG_CUJ_AUDIO_ONLY = + DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app audio only") private val TAG = "ShareToAppVM".pad() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index b0fa9d842480..d46638fac46c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -56,7 +56,8 @@ object OngoingActivityChipBinder { // Data setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore) setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView) - viewBinding.rootView.setOnClickListener(chipModel.onClickListener) + + viewBinding.rootView.setOnClickListener(chipModel.onClickListenerLegacy) updateChipPadding( chipModel, chipBackgroundView, @@ -424,7 +425,7 @@ object OngoingActivityChipBinder { // Clickable chips need to be a minimum size for accessibility purposes, but let // non-clickable chips be smaller. val minimumWidth = - if (chipModel.onClickListener != null) { + if (chipModel.onClickListenerLegacy != null) { chipBackgroundView.context.resources.getDimensionPixelSize( R.dimen.min_clickable_item_size ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index 1be5842bceeb..6ce3228531d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -35,10 +35,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.Expandable +import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth @@ -47,23 +50,42 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel @Composable fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) { + when (val clickBehavior = model.clickBehavior) { + is OngoingActivityChipModel.ClickBehavior.ExpandAction -> { + // Wrap the chip in an Expandable so we can animate the expand transition. + ExpandableChip( + color = { Color.Transparent }, + shape = + RoundedCornerShape( + dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius) + ), + modifier = modifier, + ) { expandable -> + ChipBody(model, onClick = { clickBehavior.onClick(expandable) }) + } + } + + is OngoingActivityChipModel.ClickBehavior.None -> { + ChipBody(model, modifier = modifier) + } + } +} + +@Composable +private fun ChipBody( + model: OngoingActivityChipModel.Shown, + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, +) { val context = LocalContext.current - val isClickable = model.onClickListener != null + val isClickable = onClick != {} val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible // height of the chip is determined by the height of the background of the Row below. Box( contentAlignment = Alignment.Center, - modifier = - modifier - .fillMaxHeight() - .clickable( - enabled = isClickable, - onClick = { - // TODO(b/372657935): Implement click actions. - }, - ), + modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick), ) { Row( horizontalArrangement = Arrangement.Center, @@ -206,3 +228,13 @@ private fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Mod } } } + +@Composable +private fun ExpandableChip( + color: () -> Color, + shape: Shape, + modifier: Modifier = Modifier, + content: @Composable (Expandable) -> Unit, +) { + Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt index cac25d04f1a5..25f90f9a0065 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt @@ -21,6 +21,7 @@ import android.content.res.ColorStateList import androidx.annotation.ColorInt import com.android.settingslib.Utils import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel /** Model representing how the chip in the status bar should be colored. */ sealed interface ColorsModel { @@ -55,4 +56,14 @@ sealed interface ColorsModel { override fun text(context: Context) = context.getColor(android.R.color.white) } + + companion object { + /** Converts the promoted notification colors to a [Custom] colors model. */ + fun PromotedNotificationContentModel.toCustomColorsModel(): Custom { + return Custom( + backgroundColorInt = this.colors.backgroundColor, + primaryTextColorInt = this.colors.primaryTextColor, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 956d99e46766..68c8f8cb4254 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.ui.model import android.view.View +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips @@ -46,17 +47,20 @@ sealed class OngoingActivityChipModel { open val colors: ColorsModel, /** * Listener method to invoke when this chip is clicked. If null, the chip won't be - * clickable. + * clickable. Will be deprecated after [StatusBarChipsModernization] is enabled. */ - open val onClickListener: View.OnClickListener?, + open val onClickListenerLegacy: View.OnClickListener?, + /** Data class that determines how clicks on the chip should be handled. */ + open val clickBehavior: ClickBehavior, ) : OngoingActivityChipModel() { /** This chip shows only an icon and nothing else. */ data class IconOnly( override val icon: ChipIcon, override val colors: ColorsModel, - override val onClickListener: View.OnClickListener?, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener?, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { override val logName = "Shown.Icon" } @@ -74,8 +78,9 @@ sealed class OngoingActivityChipModel { * [android.widget.Chronometer.setBase]. */ val startTimeMs: Long, - override val onClickListener: View.OnClickListener?, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener?, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { override val logName = "Shown.Timer" } @@ -88,8 +93,9 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, /** The time of the event that this chip represents. */ val time: Long, - override val onClickListener: View.OnClickListener?, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener?, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { init { StatusBarNotifChips.assertInNewMode() } @@ -105,7 +111,13 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, /** The number of seconds until an event is started. */ val secondsUntilStarted: Long, - ) : Shown(icon = null, colors, onClickListener = null) { + ) : + Shown( + icon = null, + colors, + onClickListenerLegacy = null, + clickBehavior = ClickBehavior.None, + ) { override val logName = "Shown.Countdown" } @@ -115,8 +127,9 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, // TODO(b/361346412): Enforce a max length requirement? val text: String, - override val onClickListener: View.OnClickListener? = null, - ) : Shown(icon, colors, onClickListener) { + override val onClickListenerLegacy: View.OnClickListener? = null, + override val clickBehavior: ClickBehavior, + ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) { override val logName = "Shown.Text" } } @@ -149,4 +162,13 @@ sealed class OngoingActivityChipModel { */ data class SingleColorIcon(val impl: Icon) : ChipIcon } + + /** Defines the behavior of the chip when it is clicked. */ + sealed interface ClickBehavior { + /** No specific click behavior. */ + data object None : ClickBehavior + + /** The chip expands into a dialog or activity on click. */ + data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt index 2fc366b7f078..a978c04d2a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.ui.viewmodel import android.view.View import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.res.R @@ -26,6 +27,7 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import kotlinx.coroutines.flow.StateFlow /** @@ -46,6 +48,7 @@ interface OngoingActivityChipViewModel { tag: String, ): View.OnClickListener { return View.OnClickListener { view -> + StatusBarChipsModernization.assertInLegacyMode() logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" }) val dialog = dialogDelegate.createDialog() val launchableView = @@ -55,5 +58,28 @@ interface OngoingActivityChipViewModel { dialogTransitionAnimator.showFromView(dialog, launchableView, cuj) } } + + /** + * Creates a chip click callback with an [Expandable] parameter that launches a dialog + * created by [dialogDelegate]. + */ + fun createDialogLaunchOnClickCallback( + dialogDelegate: SystemUIDialog.Delegate, + dialogTransitionAnimator: DialogTransitionAnimator, + cuj: DialogCuj, + @StatusBarChipsLog logger: LogBuffer, + tag: String, + ): (Expandable) -> Unit { + return { expandable -> + StatusBarChipsModernization.assertInNewMode() + logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" }) + val dialog = dialogDelegate.createDialog() + + val controller = expandable.dialogTransitionController(cuj) + if (controller != null) { + dialogTransitionAnimator.show(dialog, controller) + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index cf9ee619b734..826329d5da1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -39,7 +39,6 @@ import static android.service.notification.NotificationListenerService.REASON_TI import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; -import static com.android.systemui.Flags.notificationsDismissPrunedSummaries; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; @@ -278,9 +277,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { Assert.isMainThread(); checkForReentrantCall(); - if (notificationsDismissPrunedSummaries()) { - entriesToDismiss = includeSummariesToDismiss(entriesToDismiss); - } + entriesToDismiss = includeSummariesToDismiss(entriesToDismiss); final int entryCount = entriesToDismiss.size(); final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index c38b84b710bc..417e57d2205f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -34,6 +34,8 @@ import static com.android.systemui.statusbar.notification.stack.NotificationPrio import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; +import android.app.Flags; import android.app.Notification; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; @@ -1091,6 +1093,14 @@ public final class NotificationEntry extends ListEntry { } /** + * Returns whether the NotificationEntry is promoted ongoing. + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public boolean isPromotedOngoing() { + return PromotedNotificationContentModel.isPromotedForStatusBarChip(mSbn.getNotification()); + } + + /** * Sets the content needed to render this notification as a promoted notification on various * surfaces (like status bar chips and AOD). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 0c040c855368..502fb51ba5c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -76,11 +76,14 @@ constructor( val allNotificationsCountValue: Int get() = repository.activeNotifications.value.individuals.size - /** The notifications that are promoted and ongoing. Sorted by priority order. */ + /** + * The notifications that are promoted and ongoing. + * + * This *may* include ongoing call notifications if the call notification also meets promotion + * criteria. + */ val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> = if (StatusBarNotifChips.isEnabled) { - // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow - // instead of being separate. topLevelRepresentativeNotifications .map { notifs -> notifs.filter { it.promotedContent != null } } .distinctUntilChanged() @@ -98,10 +101,10 @@ constructor( val ongoingCallNotification: Flow<ActiveNotificationModel?> = allRepresentativeNotifications .map { notifMap -> - // Once a call has started, its `whenTime` should stay the same, so we can use it as - // a stable sort value. notifMap.values - .filter { it.callType == CallType.Ongoing } + .filter { it.isOngoingCallNotification() } + // Once a call has started, its `whenTime` should stay the same, so we can use + // it as a stable sort value. .minByOrNull { it.whenTime } } .distinctUntilChanged() @@ -153,4 +156,8 @@ constructor( fun setNotifStats(notifStats: NotifStats) { repository.notifStats.value = notifStats } + + companion object { + fun ActiveNotificationModel.isOngoingCallNotification() = this.callType == CallType.Ongoing + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index c88dd7af6b24..d401283aa84e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -154,10 +154,12 @@ public class FooterView extends StackScrollerDecorView { DumpUtilsKt.withIncreasedIndent(pw, () -> { // TODO: b/375010573 - update dumps for redesign pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); - pw.println("manageButton visibility: " - + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); - pw.println("dismissButton visibility: " - + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); + if (mManageOrHistoryButton != null) + pw.println("mManageOrHistoryButton visibility: " + + DumpUtilsKt.visibilityString(mManageOrHistoryButton.getVisibility())); + if (mClearAllButton != null) + pw.println("mClearAllButton visibility: " + + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index d02e17cab534..0171fb72e158 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -1425,7 +1425,12 @@ public class HeadsUpManagerImpl } } - return (mEntry.isRowPinned() && mExpanded) + // Promoted notifications are always shown as expanded, and we don't want them to ever + // be sticky. + boolean isStickyDueToExpansion = + mEntry.isRowPinned() && mExpanded && !mEntry.isPromotedOngoing(); + + return isStickyDueToExpansion || mRemoteInputActive || hasFullScreenIntent(mEntry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt index d3359d39e959..6bcce3e21998 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.logging.dagger +import com.android.app.tracing.TrackGroupUtils.trackGroup import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory @@ -44,7 +45,11 @@ object NotificationsLogModule { @SysUISingleton @NotificationHeadsUpLog fun provideNotificationHeadsUpLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifHeadsUpLog", 1000) + return factory.create( + "NotifHeadsUpLog", + 1000, + systraceTrackName = notifPipelineTrack("NotifHeadsUpLog"), + ) } /** Provides a logging buffer for logs related to inflation of notifications. */ @@ -52,7 +57,11 @@ object NotificationsLogModule { @SysUISingleton @NotifInflationLog fun provideNotifInflationLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifInflationLog", 250) + return factory.create( + "NotifInflationLog", + 250, + systraceTrackName = notifPipelineTrack("NotifInflationLog"), + ) } /** Provides a logging buffer for all logs related to the data layer of notifications. */ @@ -60,7 +69,11 @@ object NotificationsLogModule { @SysUISingleton @NotifInteractionLog fun provideNotifInteractionLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifInteractionLog", 50) + return factory.create( + "NotifInteractionLog", + 50, + systraceTrackName = notifPipelineTrack("NotifInteractionLog"), + ) } /** Provides a logging buffer for notification interruption calculations. */ @@ -68,7 +81,11 @@ object NotificationsLogModule { @SysUISingleton @NotificationInterruptLog fun provideNotificationInterruptLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifInterruptLog", 100) + return factory.create( + "NotifInterruptLog", + 100, + systraceTrackName = notifPipelineTrack("NotifInterruptLog"), + ) } /** Provides a logging buffer for all logs related to notifications on the lockscreen. */ @@ -91,7 +108,12 @@ object NotificationsLogModule { if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) { maxSize *= 10 } - return factory.create("NotifLog", maxSize, Compile.IS_DEBUG /* systrace */) + return factory.create( + "NotifLog", + maxSize, + /* systrace= */ Compile.IS_DEBUG, + systraceTrackName = notifPipelineTrack("NotifLog"), + ) } /** Provides a logging buffer for all logs related to remote input controller. */ @@ -107,7 +129,11 @@ object NotificationsLogModule { @SysUISingleton @NotificationRenderLog fun provideNotificationRenderLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifRenderLog", 100) + return factory.create( + "NotifRenderLog", + 100, + systraceTrackName = notifPipelineTrack("NotifRenderLog"), + ) } /** Provides a logging buffer for all logs related to managing notification sections. */ @@ -150,3 +176,13 @@ object NotificationsLogModule { return factory.create("VisualStabilityLog", 50, /* maxSize */ false /* systrace */) } } + +private const val NOTIF_PIPELINE_TRACK_GROUP_NAME = "Notification pipeline" + +/** + * This generates a track name that is hierarcically collapsed inside + * [NOTIF_PIPELINE_TRACK_GROUP_NAME] in perfetto traces. + */ +private fun notifPipelineTrack(trackName: String): String { + return trackGroup(NOTIF_PIPELINE_TRACK_GROUP_NAME, trackName) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 258d80c60cd7..a175f90c384a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -147,6 +147,7 @@ data class PromotedNotificationContentModel( * Returns true if the given notification should be considered promoted when deciding * whether or not to show the status bar chip UI. */ + @JvmStatic fun isPromotedForStatusBarChip(notification: Notification): Boolean { // Notification.isPromotedOngoing checks the ui_rich_ongoing flag, but we want the // status bar chip to be ready before all the features behind the ui_rich_ongoing flag diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index e27ff7d6746b..793b3b8b1e42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -17,39 +17,210 @@ package com.android.systemui.statusbar.notification.row import android.content.Context +import android.graphics.BlendMode import android.graphics.Canvas +import android.graphics.Color import android.graphics.ColorFilter +import android.graphics.LinearGradient import android.graphics.Paint +import android.graphics.Path import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.RuntimeShader +import android.graphics.Shader import android.graphics.drawable.Drawable +import android.widget.FrameLayout +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.android.internal.graphics.ColorUtils +import com.android.systemui.res.R +import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary +import kotlin.math.max +import kotlin.math.roundToInt /** - * A background style for smarter-smart-actions. - * - * TODO(b/383567383) implement final UX + * A background style for smarter-smart-actions. The style is composed by a simplex3d noise, + * overlaid with sparkles. */ -class MagicActionBackgroundDrawable(context: Context) : Drawable() { +class MagicActionBackgroundDrawable( + context: Context, + primaryContainer: Int? = null, + private val seed: Float = 0f, +) : Drawable() { + + private val pixelDensity = context.resources.displayMetrics.density + private val cornerRadius = + context.resources.getDimensionPixelSize(R.dimen.smart_reply_button_corner_radius).toFloat() + private val outlineStrokeWidth = + context.resources + .getDimensionPixelSize(R.dimen.smart_action_button_outline_stroke_width) + .toFloat() + private val buttonShape = Path() + private val paddingVertical = + context.resources + .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical) + .toFloat() + + /** The color of the button background. */ + private val mainColor = + primaryContainer + ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) - private var _alpha: Int = 255 - private var _colorFilter: ColorFilter? = null - private val paint = - Paint().apply { - color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) + /** Slightly dimmed down version of [mainColor] used on the simplex noise. */ + private val dimColor: Int + get() { + val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray() + ColorUtils.colorToLAB(mainColor, labColor) + val camColor = ColorUtils.colorToCAM(mainColor) + return ColorUtils.CAMToColor( + camColor.hue, + camColor.chroma, + max(0f, (labColor[0] - 20).toFloat()), + ) } + private val bgShader = MagicActionBackgroundShader() + private val bgPaint = Paint() + private val outlinePaint = Paint() + + init { + bgShader.setColorUniform("in_color", mainColor) + bgShader.setColorUniform("in_dimColor", dimColor) + bgPaint.shader = bgShader + outlinePaint.style = Paint.Style.STROKE + // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge + // of the rectangle. + outlinePaint.strokeWidth = outlineStrokeWidth * 2 + outlinePaint.blendMode = BlendMode.SCREEN + outlinePaint.alpha = (255 * 0.32f).roundToInt() + } + override fun draw(canvas: Canvas) { - canvas.drawRect(bounds, paint) + // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where + // around the button background and the outline. + canvas.clipPath(buttonShape) + + canvas.drawRect(bounds, bgPaint) + canvas.drawRoundRect( + bounds.left.toFloat(), + bounds.top + paddingVertical, + bounds.right.toFloat(), + bounds.bottom - paddingVertical, + cornerRadius, + cornerRadius, + outlinePaint, + ) + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + + val width = bounds.width().toFloat() + val height = bounds.height() - paddingVertical * 2 + if (width == 0f || height == 0f) return + + bgShader.setFloatUniform("in_gridNum", NOISE_SIZE) + bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA) + bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f) + bgShader.setFloatUniform("in_size", width, height) + bgShader.setFloatUniform("in_aspectRatio", width / height) + bgShader.setFloatUniform("in_time", seed) + bgShader.setFloatUniform("in_pixelDensity", pixelDensity) + + buttonShape.reset() + buttonShape.addRoundRect( + bounds.left.toFloat(), + bounds.top + paddingVertical, + bounds.right.toFloat(), + bounds.bottom - paddingVertical, + cornerRadius, + cornerRadius, + Path.Direction.CW, + ) + + val outlineGradient = + LinearGradient( + bounds.left.toFloat(), + 0f, + bounds.right.toFloat(), + 0f, + mainColor, + ColorUtils.setAlphaComponent(mainColor, 0), + Shader.TileMode.CLAMP, + ) + outlinePaint.shader = outlineGradient } override fun setAlpha(alpha: Int) { - _alpha = alpha + bgPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { - _colorFilter = colorFilter + bgPaint.colorFilter = colorFilter invalidateSelf() } override fun getOpacity(): Int = PixelFormat.TRANSLUCENT + + companion object { + /** Smoothness of the turbulence. Larger numbers yield more detail. */ + private const val NOISE_SIZE = 0.7f + + /** Strength of the sparkles overlaid on the turbulence. */ + private const val SPARKLE_ALPHA = 0.15f + } +} + +private class MagicActionBackgroundShader : RuntimeShader(SHADER) { + + // language=AGSL + companion object { + private const val UNIFORMS = + """ + uniform float in_gridNum; + uniform vec3 in_noiseMove; + uniform vec2 in_size; + uniform float in_aspectRatio; + uniform half in_time; + uniform half in_pixelDensity; + uniform float in_spkarkleAlpha; + layout(color) uniform vec4 in_color; + layout(color) uniform vec4 in_dimColor; + """ + private const val MAIN_SHADER = + """vec4 main(vec2 p) { + vec2 uv = p / in_size.xy; + uv.x *= in_aspectRatio; + vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; + half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP))); + half4 turbulenceColor = mix(in_color, in_dimColor, luma); + float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time); + sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha); + return turbulenceColor + half4(half3(sparkle), 1.0); + } + """ + private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER + } +} + +// @Preview +@Composable +fun DrawablePreview() { + AndroidView( + factory = { context -> + FrameLayout(context).apply { + background = + MagicActionBackgroundDrawable( + context = context, + primaryContainer = Color.parseColor("#c5eae2"), + seed = 0f, + ) + } + }, + modifier = Modifier.size(100.dp, 50.dp), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 495b50869458..f57107141f61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -127,7 +127,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState; import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; @@ -282,11 +281,9 @@ public class NotificationStackScrollLayout private boolean mExpandedInThisMotion; private boolean mShouldShowShelfOnly; protected boolean mScrollingEnabled; - private boolean mIsCurrentUserSetup; protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mClearAllInProgress; - private FooterClearAllListener mFooterClearAllListener; private boolean mFlingAfterUpEvent; /** * Was the scroller scrolled to the top when the down motion was observed? @@ -467,7 +464,6 @@ public class NotificationStackScrollLayout boolean mHeadsUpAnimatingAway; private Consumer<Boolean> mHeadsUpAnimatingAwayListener; private int mStatusBarState; - private int mUpcomingStatusBarState; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; private final Runnable mReflingAndAnimateScroll = this::animateScroll; private int mCornerRadius; @@ -498,7 +494,6 @@ public class NotificationStackScrollLayout private float mLastSentExpandedHeight; private boolean mWillExpand; private int mGapHeight; - private boolean mIsRemoteInputActive; /** * The extra inset during the full shade transition @@ -572,10 +567,8 @@ public class NotificationStackScrollLayout private boolean mDismissUsingRowTranslationX = true; private ExpandableNotificationRow mTopHeadsUpRow; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; - private final ScreenOffAnimationController mScreenOffAnimationController; private boolean mShouldUseSplitNotificationShade; private boolean mShouldSkipTopPaddingAnimationAfterFold = false; - private boolean mHasFilteredOutSeenNotifications; @Nullable private SplitShadeStateController mSplitShadeStateController = null; private boolean mIsSmallLandscapeLockscreenEnabled = false; private boolean mSuppressHeightUpdates; @@ -636,9 +629,6 @@ public class NotificationStackScrollLayout }; @Nullable - private OnClickListener mManageButtonClickListener; - - @Nullable private WallpaperInteractor mWallpaperInteractor; public NotificationStackScrollLayout(Context context, AttributeSet attrs) { @@ -650,8 +640,6 @@ public class NotificationStackScrollLayout mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSectionsManager = Dependency.get(NotificationSectionsManager.class); - mScreenOffAnimationController = - Dependency.get(ScreenOffAnimationController.class); mSectionsManager.initialize(this); mSections = mSectionsManager.createSectionsForBuckets(); @@ -5403,7 +5391,6 @@ public class NotificationStackScrollLayout println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout); println(pw, "scrollY", mAmbientState.getScrollY()); println(pw, "showShelfOnly", mShouldShowShelfOnly); - println(pw, "isCurrentUserSetup", mIsCurrentUserSetup); println(pw, "hideAmount", mAmbientState.getHideAmount()); println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp()); println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications); @@ -6793,10 +6780,6 @@ public class NotificationStackScrollLayout void onClearAll(@SelectedRows int selectedRows); } - interface FooterClearAllListener { - void onClearAll(); - } - interface ClearAllAnimationListener { void onAnimationEnd( List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f0455fc3a22b..c1d022600559 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -49,9 +49,11 @@ import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel @@ -131,9 +133,12 @@ constructor( private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, + private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel, dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, + private val dozingToPrimaryBouncerTransitionViewModel: + DozingToPrimaryBouncerTransitionViewModel, private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, @@ -554,8 +559,10 @@ constructor( aodToGoneTransitionViewModel.notificationAlpha(viewState), aodToLockscreenTransitionViewModel.notificationAlpha, aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), + aodToPrimaryBouncerTransitionViewModel.notificationAlpha, dozingToLockscreenTransitionViewModel.lockscreenAlpha, dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), + dozingToPrimaryBouncerTransitionViewModel.notificationAlpha, dreamingToLockscreenTransitionViewModel.lockscreenAlpha, goneToAodTransitionViewModel.notificationAlpha, goneToDreamingTransitionViewModel.lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 4751293a16cc..5a63c0cd84e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -312,6 +312,25 @@ constructor( } } + override fun postStartActivityDismissingKeyguard( + intent: Intent, + delay: Int, + animationController: ActivityTransitionAnimator.Controller?, + customMessage: String?, + userHandle: UserHandle?, + ) { + postOnUiThread(delay) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = true, + dismissShade = true, + animationController = animationController, + customMessage = customMessage, + userHandle = userHandle, + ) + } + } + override fun dismissKeyguardThenExecute( action: OnDismissAction, cancel: Runnable?, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index c47ed1722bb4..fcc3af6a2134 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -397,6 +397,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); mDelegate.onWindowFocusChanged(this, hasFocus); + if (hasFocus) { + // Update SysUI state to reflect that a dialog is showing. This ensures the state is + // correct when this dialog regains focus after another dialog was closed. + // See b/386871258 + mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true) + .commitUpdate(mContext.getDisplayId()); + } } public void setShowForAllUsers(boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 4eb69babfadb..a29934fa3a16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipChronometer import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository @@ -159,6 +160,7 @@ constructor( notificationIconView = currentInfo.notificationIconView, intent = currentInfo.intent, notificationKey = currentInfo.key, + promotedContent = currentInfo.promotedContent, ) } else { return OngoingCallModel.NoCall @@ -215,6 +217,7 @@ constructor( notifModel.statusBarChipIconView, notifModel.contentIntent, notifModel.uid, + notifModel.promotedContent, isOngoing = true, statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false, ) @@ -334,6 +337,11 @@ constructor( val notificationIconView: StatusBarIconView?, val intent: PendingIntent?, val uid: Int, + /** + * If the call notification also meets promoted notification criteria, this field is filled + * in with the content related to promotion. Otherwise null. + */ + val promotedContent: PromotedNotificationContentModel?, /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */ val isOngoing: Boolean, /** True if the user has swiped away the status bar while in this phone call. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt index 2ab2b68bbb2e..9f8b45578903 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt @@ -1,18 +1,18 @@ /* -* 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. -*/ + * 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.statusbar.phone.ongoingcall import com.android.systemui.Flags @@ -44,9 +44,16 @@ object StatusBarChipsModernization { RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) /** + * Called to ensure code is only run when the flag is enabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt index 2bfbf4826053..99141f592a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -165,6 +165,7 @@ constructor( notificationIconView = model.statusBarChipIconView, intent = model.contentIntent, notificationKey = model.key, + promotedContent = model.promotedContent, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt index 1a5dcc16f3db..7d00e9d58e5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.shared.model import android.app.PendingIntent import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel /** Represents the state of any ongoing calls. */ sealed interface OngoingCallModel { @@ -25,8 +26,8 @@ sealed interface OngoingCallModel { data object NoCall : OngoingCallModel /** - * There is an ongoing call but the call app is currently visible, so we don't need to show - * the chip. + * There is an ongoing call but the call app is currently visible, so we don't need to show the + * chip. */ data object InCallWithVisibleApp : OngoingCallModel @@ -41,11 +42,14 @@ sealed interface OngoingCallModel { * @property notificationIconView the [android.app.Notification.getSmallIcon] that's set on the * call notification. We may use this icon in the chip instead of the default phone icon. * @property intent the intent associated with the call notification. + * @property promotedContent if the call notification also meets promoted notification criteria, + * this field is filled in with the content related to promotion. Otherwise null. */ data class InCall( val startTimeMs: Long, val notificationIconView: StatusBarIconView?, val intent: PendingIntent?, val notificationKey: String, + val promotedContent: PromotedNotificationContentModel?, ) : OngoingCallModel } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt index b32037992501..dacb859967e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt @@ -111,7 +111,12 @@ constructor( pw.println("Carrier configs by subId") configs.keyIterator().forEach { pw.println(" subId=$it") - pw.println(" config=${configs.get(it).toStringConsideringDefaults()}") + val config = configs.get(it) + if (config == null) { + pw.println(" config=null (config was removed during dump)") + } else { + pw.println(" config=${config.toStringConsideringDefaults()}") + } } // Finally, print the default config pw.println("Default config:") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt index 8daa8037c367..7e76d77abe61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt @@ -41,8 +41,8 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedD import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization +import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel import javax.inject.Inject import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index b1cc208e9b43..9c1171fd1ebc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener +import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory import javax.inject.Inject @@ -304,11 +305,7 @@ fun StatusBarRoot( fun Disambiguation(viewModel: HomeStatusBarViewModel) { val clockVisibilityModel = viewModel.isClockVisible.collectAsStateWithLifecycle( - initialValue = - HomeStatusBarViewModel.VisibilityModel( - visibility = View.GONE, - shouldAnimateChange = false, - ) + initialValue = VisibilityModel(visibility = View.GONE, shouldAnimateChange = false) ) if (clockVisibilityModel.value.visibility == View.VISIBLE) { Box(modifier = Modifier.fillMaxSize().alpha(0.5f), contentAlignment = Alignment.Center) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt new file mode 100644 index 000000000000..e27225270633 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.model + +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState + +/** The combined visibility + animation state for the system info status bar area */ +data class SystemInfoCombinedVisibilityModel( + val baseVisibility: VisibilityModel, + val animationState: SystemEventAnimationState, +) : Diffable<SystemInfoCombinedVisibilityModel> { + override fun logDiffs(prevVal: SystemInfoCombinedVisibilityModel, row: TableRowLogger) { + if (animationState != prevVal.animationState) { + row.logChange(COL_ANIM, animationState.name) + } + + baseVisibility.logDiffs(prevVal.baseVisibility, row) + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_ANIM, animationState.name) + baseVisibility.logFull(row) + } + + companion object { + const val COL_ANIM = "animState" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt new file mode 100644 index 000000000000..7b39ada72c64 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.model + +import android.view.View +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.util.visibilityString + +/** Models the current visibility for a specific child view of status bar. */ +data class VisibilityModel( + @View.Visibility val visibility: Int, + /** True if a visibility change should be animated. */ + val shouldAnimateChange: Boolean, +) : Diffable<VisibilityModel> { + override fun logDiffs(prevVal: VisibilityModel, row: TableRowLogger) { + if (visibility != prevVal.visibility) { + row.logChange(COL_VIS, visibilityString(visibility)) + } + + if (shouldAnimateChange != prevVal.shouldAnimateChange) { + row.logChange(COL_ANIMATE, shouldAnimateChange) + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_VIS, visibilityString(visibility)) + row.logChange(COL_ANIMATE, shouldAnimateChange) + } + + companion object { + const val COL_VIS = "vis" + const val COL_ANIMATE = "animate" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index dcd2dbf57b42..5acedf129184 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -28,6 +28,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -39,7 +41,6 @@ import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChip import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor -import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel @@ -53,7 +54,8 @@ import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor -import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel +import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel +import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -155,19 +157,6 @@ interface HomeStatusBarViewModel { */ val areaTint: Flow<StatusBarTintColor> - /** Models the current visibility for a specific child view of status bar. */ - data class VisibilityModel( - @View.Visibility val visibility: Int, - /** True if a visibility change should be animated. */ - val shouldAnimateChange: Boolean, - ) - - /** The combined visibility + animation state for the system info status bar area */ - data class SystemInfoCombinedVisibilityModel( - val baseVisibility: VisibilityModel, - val animationState: SystemEventAnimationState, - ) - /** Interface for the assisted factory, to allow for providing a fake in tests */ interface HomeStatusBarViewModelFactory { fun create(displayId: Int): HomeStatusBarViewModel @@ -178,6 +167,7 @@ class HomeStatusBarViewModelImpl @AssistedInject constructor( @Assisted thisDisplayId: Int, + tableLoggerFactory: TableLogBufferFactory, homeStatusBarInteractor: HomeStatusBarInteractor, homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor, lightsOutInteractor: LightsOutInteractor, @@ -196,9 +186,19 @@ constructor( statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore, @Application coroutineScope: CoroutineScope, ) : HomeStatusBarViewModel { + + val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200) + override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> = keyguardTransitionInteractor .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED)) + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = "", + columnName = COL_LOCK_TO_OCCLUDED, + initialValue = false, + ) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> = @@ -225,20 +225,33 @@ constructor( // which lives elsewhere.) currentScene == Scenes.Gone || isOccluded } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = "", + columnName = COL_ALLOWED_BY_SCENE, + initialValue = false, + ) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) override val areNotificationsLightsOut: Flow<Boolean> = if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) { - emptyFlow() - } else { - combine( - notificationsInteractor.areAnyNotificationsPresent, - lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false), - ) { hasNotifications, isLowProfile -> - hasNotifications && isLowProfile - } - .distinctUntilChanged() - } + emptyFlow() + } else { + combine( + notificationsInteractor.areAnyNotificationsPresent, + lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false), + ) { hasNotifications, isLowProfile -> + hasNotifications && isLowProfile + } + .distinctUntilChanged() + } + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = "", + columnName = COL_NOTIF_LIGHTS_OUT, + initialValue = false, + ) override val areaTint: Flow<StatusBarTintColor> = darkIconInteractor @@ -277,19 +290,26 @@ constructor( override val shouldHomeStatusBarBeVisible = combine( - isHomeStatusBarAllowed, - keyguardInteractor.isSecureCameraActive, - headsUpNotificationInteractor.statusBarHeadsUpStatus, - ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState -> - // When launching the camera over the lockscreen, the status icons would typically - // become visible momentarily before animating out, since we're not yet aware that the - // launching camera activity is fullscreen. Even once the activity finishes launching, - // it takes a short time before WM decides that the top app wants to hide the icons and - // tells us to hide them. - // To ensure that this high-visibility animation is smooth, keep the icons hidden during - // a camera launch. See b/257292822. - headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive) - } + isHomeStatusBarAllowed, + keyguardInteractor.isSecureCameraActive, + headsUpNotificationInteractor.statusBarHeadsUpStatus, + ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState -> + // When launching the camera over the lockscreen, the status icons would typically + // become visible momentarily before animating out, since we're not yet aware that + // the launching camera activity is fullscreen. Even once the activity finishes + // launching, it takes a short time before WM decides that the top app wants to hide + // the icons and tells us to hide them. To ensure that this high-visibility + // animation is smooth, keep the icons hidden during a camera launch. See + // b/257292822. + headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive) + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = "", + columnName = COL_VISIBLE, + initialValue = false, + ) private val isAnyChipVisible = if (StatusBarNotifChips.isEnabled) { @@ -313,53 +333,73 @@ constructor( override val shouldShowOperatorNameView: Flow<Boolean> = combine( - shouldHomeStatusBarBeVisible, - hideStartSideContentForHeadsUp, - homeStatusBarInteractor.visibilityViaDisableFlags, - homeStatusBarInteractor.shouldShowOperatorName, - ) { - shouldStatusBarBeVisible, - hideStartSideContentForHeadsUp, - visibilityViaDisableFlags, - shouldShowOperator -> - shouldStatusBarBeVisible && - !hideStartSideContentForHeadsUp && - visibilityViaDisableFlags.isSystemInfoAllowed && - shouldShowOperator - } + shouldHomeStatusBarBeVisible, + hideStartSideContentForHeadsUp, + homeStatusBarInteractor.visibilityViaDisableFlags, + homeStatusBarInteractor.shouldShowOperatorName, + ) { + shouldStatusBarBeVisible, + hideStartSideContentForHeadsUp, + visibilityViaDisableFlags, + shouldShowOperator -> + shouldStatusBarBeVisible && + !hideStartSideContentForHeadsUp && + visibilityViaDisableFlags.isSystemInfoAllowed && + shouldShowOperator + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = "", + columnName = COL_SHOW_OPERATOR_NAME, + initialValue = false, + ) override val isClockVisible: Flow<VisibilityModel> = combine( - shouldHomeStatusBarBeVisible, - hideStartSideContentForHeadsUp, - homeStatusBarInteractor.visibilityViaDisableFlags, - ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags -> - val showClock = - shouldStatusBarBeVisible && - visibilityViaDisableFlags.isClockAllowed && - !hideStartSideContentForHeadsUp - // Always use View.INVISIBLE here, so that animations work - VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate) - } + shouldHomeStatusBarBeVisible, + hideStartSideContentForHeadsUp, + homeStatusBarInteractor.visibilityViaDisableFlags, + ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags + -> + val showClock = + shouldStatusBarBeVisible && + visibilityViaDisableFlags.isClockAllowed && + !hideStartSideContentForHeadsUp + // Always use View.INVISIBLE here, so that animations work + VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate) + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = COL_PREFIX_CLOCK, + initialValue = VisibilityModel(false.toVisibleOrInvisible(), false), + ) override val isNotificationIconContainerVisible: Flow<VisibilityModel> = combine( - shouldHomeStatusBarBeVisible, - isAnyChipVisible, - homeStatusBarInteractor.visibilityViaDisableFlags, - ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags -> - val showNotificationIconContainer = - if (anyChipVisible) { - false - } else { - shouldStatusBarBeVisible && - visibilityViaDisableFlags.areNotificationIconsAllowed - } - VisibilityModel( - showNotificationIconContainer.toVisibleOrGone(), - visibilityViaDisableFlags.animate, + shouldHomeStatusBarBeVisible, + isAnyChipVisible, + homeStatusBarInteractor.visibilityViaDisableFlags, + ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags -> + val showNotificationIconContainer = + if (anyChipVisible) { + false + } else { + shouldStatusBarBeVisible && + visibilityViaDisableFlags.areNotificationIconsAllowed + } + VisibilityModel( + showNotificationIconContainer.toVisibleOrGone(), + visibilityViaDisableFlags.animate, + ) + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = COL_PREFIX_NOTIF_CONTAINER, + initialValue = VisibilityModel(false.toVisibleOrInvisible(), false), ) - } private val isSystemInfoVisible = combine(shouldHomeStatusBarBeVisible, homeStatusBarInteractor.visibilityViaDisableFlags) { @@ -372,18 +412,19 @@ constructor( override val systemInfoCombinedVis = combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState -> - HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel( - sysInfoVisible, - animationState, - ) + SystemInfoCombinedVisibilityModel(sysInfoVisible, animationState) } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogger, + columnPrefix = COL_PREFIX_SYSTEM_INFO, + initialValue = + SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle), + ) .stateIn( coroutineScope, SharingStarted.WhileSubscribed(), - HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel( - VisibilityModel(View.VISIBLE, false), - Idle, - ), + SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle), ) override val iconBlockList: Flow<List<String>> = @@ -408,6 +449,19 @@ constructor( HomeStatusBarViewModel.HomeStatusBarViewModelFactory { override fun create(displayId: Int): HomeStatusBarViewModelImpl } + + companion object { + private const val COL_LOCK_TO_OCCLUDED = "Lock->Occluded" + private const val COL_ALLOWED_BY_SCENE = "allowedByScene" + private const val COL_NOTIF_LIGHTS_OUT = "notifLightsOut" + private const val COL_SHOW_OPERATOR_NAME = "showOperatorName" + private const val COL_VISIBLE = "visible" + private const val COL_PREFIX_CLOCK = "clock" + private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer" + private const val COL_PREFIX_SYSTEM_INFO = "systemInfo" + + fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]" + } } /** Lookup the color for a given view in the status bar */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index cb26679434ef..520c5632370b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -408,6 +408,11 @@ constructor( action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false) ) { background = MagicActionBackgroundDrawable(parent.context) + val textColor = + parent.context.getColor( + com.android.internal.R.color.materialColorOnPrimaryContainer + ) + setTextColor(textColor) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt index 9ff0d18f0e2b..db5f1301823b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy.ui.dialog +import android.app.Dialog +import android.content.Context import android.content.Intent import android.provider.Settings import android.util.Log @@ -36,6 +38,7 @@ import com.android.compose.PlatformOutlinedButton import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor +import com.android.settingslib.notification.modes.EnableDndDialogFactory import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable @@ -43,6 +46,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dialog.ui.composable.AlertDialogContent import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.ComponentSystemUIDialog @@ -61,6 +65,7 @@ import kotlinx.coroutines.withContext class ModesDialogDelegate @Inject constructor( + val context: Context, private val sysuiDialogFactory: SystemUIDialogFactory, private val dialogTransitionAnimator: DialogTransitionAnimator, private val activityStarter: ActivityStarter, @@ -72,6 +77,7 @@ constructor( ) : SystemUIDialog.Delegate { // NOTE: This should only be accessed/written from the main thread. @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null + private val dndDurationDialogLogger by lazy { QSEnableDndDialogMetricsLogger(context) } override fun createDialog(): SystemUIDialog { Assert.isMainThread() @@ -195,6 +201,26 @@ constructor( activityStarter.startActivity(intent, /* dismissShade= */ true, animationController) } + /** + * Special dialog to ask the user for the duration of DND. Not to be confused with the modes + * dialog itself. + */ + fun makeDndDurationDialog(): Dialog { + val dialog = + EnableDndDialogFactory( + context, + R.style.Theme_SystemUI_Dialog, + /* cancelIsNeutral= */ true, + dndDurationDialogLogger, + ) + .createDialog() + SystemUIDialog.applyFlags(dialog) + SystemUIDialog.setShowForAllUsers(dialog, true) + SystemUIDialog.registerDismissListener(dialog) + SystemUIDialog.setDialogSize(dialog) + return dialog + } + companion object { private const val TAG = "ModesDialogDelegate" private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt index 1c13a833ef30..07f1c3470c83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt @@ -16,20 +16,16 @@ package com.android.systemui.statusbar.policy.ui.dialog.viewmodel -import android.app.Dialog import android.content.Context import android.content.Intent import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID -import com.android.settingslib.notification.modes.EnableZenModeDialog import com.android.settingslib.notification.modes.ZenMode import com.android.settingslib.notification.modes.ZenModeDescriptions import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger import com.android.systemui.res.R -import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger @@ -54,7 +50,6 @@ constructor( private val dialogDelegate: ModesDialogDelegate, private val dialogEventLogger: ModesDialogEventLogger, ) { - private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context) private val zenModeDescriptions = ZenModeDescriptions(context) // Modes that should be displayed in the dialog @@ -112,7 +107,7 @@ constructor( if (zenModeInteractor.shouldAskForZenDuration(mode)) { dialogEventLogger.logOpenDurationDialog(mode) // NOTE: The dialog handles turning on the mode itself. - val dialog = makeZenModeDialog() + val dialog = dialogDelegate.makeDndDurationDialog() dialog.show() } else { dialogEventLogger.logModeOn(mode) @@ -173,20 +168,4 @@ constructor( modeDescription ?: context.getString(R.string.zen_mode_off) } } - - private fun makeZenModeDialog(): Dialog { - val dialog = - EnableZenModeDialog( - context, - R.style.Theme_SystemUI_Dialog, - /* cancelIsNeutral= */ true, - zenDialogMetricsLogger, - ) - .createDialog() - SystemUIDialog.applyFlags(dialog) - SystemUIDialog.setShowForAllUsers(dialog, true) - SystemUIDialog.registerDismissListener(dialog) - SystemUIDialog.setDialogSize(dialog) - return dialog - } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt index c43f31beb5bc..a2125c8f0955 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt @@ -39,6 +39,8 @@ import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureRecognizer import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureRecognizerProvider import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureRecognizerProvider +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -69,6 +71,14 @@ interface TouchpadTutorialModule { } @Provides + fun switchAppsViewModel( + recognizerProvider: SwitchAppsGestureRecognizerProvider, + adapterFactory: GestureRecognizerAdapter.Factory, + ): SwitchAppsGestureScreenViewModel { + return SwitchAppsGestureScreenViewModel(adapterFactory.create(recognizerProvider)) + } + + @Provides fun recentAppsViewModel( recognizerProvider: RecentAppsGestureRecognizerProvider, adapterFactory: GestureRecognizerAdapter.Factory, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt new file mode 100644 index 000000000000..3bb0dd779613 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/SwitchAppsGestureTutorialScreen.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2025 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.touchpad.tutorial.ui.composable + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.airbnb.lottie.compose.rememberLottieDynamicProperties +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig +import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel + +@Composable +fun SwitchAppsGestureTutorialScreen( + viewModel: SwitchAppsGestureScreenViewModel, + easterEggGestureViewModel: EasterEggGestureViewModel, + onDoneButtonClicked: () -> Unit, + onBack: () -> Unit, +) { + val screenConfig = + TutorialScreenConfig( + colors = rememberScreenColors(), + strings = + TutorialScreenConfig.Strings( + titleResId = R.string.touchpad_switch_apps_gesture_action_title, + bodyResId = R.string.touchpad_switch_apps_gesture_guidance, + titleSuccessResId = R.string.touchpad_switch_apps_gesture_success_title, + bodySuccessResId = R.string.touchpad_switch_apps_gesture_success_body, + titleErrorResId = R.string.gesture_error_title, + bodyErrorResId = R.string.touchpad_switch_gesture_error_body, + ), + // TODO: replace animation + animations = TutorialScreenConfig.Animations(educationResId = R.raw.trackpad_back_edu), + ) + GestureTutorialScreen( + screenConfig = screenConfig, + tutorialStateFlow = viewModel.tutorialState, + motionEventConsumer = { + easterEggGestureViewModel.accept(it) + viewModel.handleEvent(it) + }, + easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered, + onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished, + onDoneButtonClicked = onDoneButtonClicked, + onBack = onBack, + ) +} + +@Composable +private fun rememberScreenColors(): TutorialScreenConfig.Colors { + val onTertiary = MaterialTheme.colorScheme.onTertiary + val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed + val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant + val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim + val dynamicProperties = + rememberLottieDynamicProperties( + rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim), + rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed), + rememberColorFilterProperty(".onTertiary", onTertiary), + rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant), + ) + val screenColors = + remember(dynamicProperties) { + TutorialScreenConfig.Colors( + background = onTertiaryFixed, + title = tertiaryFixedDim, + animationColors = dynamicProperties, + ) + } + return screenColors +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index c8a58400069e..69b7e892a380 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -61,6 +61,7 @@ fun TutorialSelectionScreen( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, onDoneButtonClicked: () -> Unit, lastSelectedScreen: Screen, ) { @@ -86,6 +87,7 @@ fun TutorialSelectionScreen( onBackTutorialClicked = onBackTutorialClicked, onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).padding(60.dp), lastSelectedScreen, ) @@ -95,6 +97,7 @@ fun TutorialSelectionScreen( onBackTutorialClicked = onBackTutorialClicked, onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).padding(60.dp), lastSelectedScreen, ) @@ -113,6 +116,7 @@ private fun HorizontalSelectionButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, lastSelectedScreen: Screen, ) { @@ -121,10 +125,11 @@ private fun HorizontalSelectionButtons( verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { - ThreeTutorialButtons( + FourTutorialButtons( onBackTutorialClicked, onHomeTutorialClicked, onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, ) @@ -136,6 +141,7 @@ private fun VerticalSelectionButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, lastSelectedScreen: Screen, ) { @@ -144,10 +150,11 @@ private fun VerticalSelectionButtons( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { - ThreeTutorialButtons( + FourTutorialButtons( onBackTutorialClicked, onHomeTutorialClicked, onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked, modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, ) @@ -155,21 +162,24 @@ private fun VerticalSelectionButtons( } @Composable -private fun ThreeTutorialButtons( +private fun FourTutorialButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, lastSelectedScreen: Screen, ) { val homeFocusRequester = remember { FocusRequester() } val backFocusRequester = remember { FocusRequester() } val recentAppsFocusRequester = remember { FocusRequester() } + val switchAppsFocusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { when (lastSelectedScreen) { Screen.HOME_GESTURE -> homeFocusRequester.requestFocus() Screen.BACK_GESTURE -> backFocusRequester.requestFocus() Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus() + Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus() else -> {} // No-Op. } } @@ -197,6 +207,14 @@ private fun ThreeTutorialButtons( backgroundColor = MaterialTheme.colorScheme.secondary, modifier = modifier.focusRequester(recentAppsFocusRequester).focusable(), ) + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon), + iconColor = MaterialTheme.colorScheme.primary, + onClick = onSwitchAppsTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.onPrimary, + modifier = modifier.focusRequester(switchAppsFocusRequester).focusable(), + ) } @Composable @@ -227,7 +245,7 @@ private fun TutorialButton( tint = iconColor, ) Spacer(modifier = Modifier.height(16.dp)) - Text(text = text, style = MaterialTheme.typography.headlineLarge) + Text(text = text, style = MaterialTheme.typography.headlineLarge, color = iconColor) } } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt new file mode 100644 index 000000000000..470048bd3b20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 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.touchpad.tutorial.ui.gesture + +import android.view.MotionEvent + +// TODO: javadoc +class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { + + private val distanceTracker = DistanceTracker() + private var gestureStateChangedCallback: (GestureState) -> Unit = {} + + override fun addGestureStateCallback(callback: (GestureState) -> Unit) { + gestureStateChangedCallback = callback + } + + override fun clearGestureStateCallback() { + gestureStateChangedCallback = {} + } + + // TODO: recognizer logic + override fun accept(event: MotionEvent) { + if (!isMultifingerTouchpadSwipe(event)) return + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index 3264300ed908..0a139125afa2 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -37,6 +37,7 @@ import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen +import com.android.systemui.touchpad.tutorial.ui.composable.SwitchAppsGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel @@ -45,7 +46,9 @@ import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScre import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.SWITCH_APPS_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION +import com.android.systemui.touchpad.tutorial.ui.viewmodel.SwitchAppsGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.TouchpadTutorialViewModel import javax.inject.Inject @@ -58,6 +61,7 @@ constructor( private val backGestureViewModel: BackGestureScreenViewModel, private val homeGestureViewModel: HomeGestureScreenViewModel, private val recentAppsGestureViewModel: RecentAppsGestureScreenViewModel, + private val switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel, private val easterEggGestureViewModel: EasterEggGestureViewModel, ) : ComponentActivity() { @@ -75,6 +79,7 @@ constructor( backGestureViewModel, homeGestureViewModel, recentAppsGestureViewModel, + switchAppsGestureScreenViewModel, easterEggGestureViewModel, closeTutorial = ::finishTutorial, ) @@ -108,6 +113,7 @@ fun TouchpadTutorialScreen( backGestureViewModel: BackGestureScreenViewModel, homeGestureViewModel: HomeGestureScreenViewModel, recentAppsGestureViewModel: RecentAppsGestureScreenViewModel, + switchAppsGestureScreenViewModel: SwitchAppsGestureScreenViewModel, easterEggGestureViewModel: EasterEggGestureViewModel, closeTutorial: () -> Unit, ) { @@ -128,6 +134,10 @@ fun TouchpadTutorialScreen( lastSelectedScreen = RECENT_APPS_GESTURE vm.goTo(RECENT_APPS_GESTURE) }, + onSwitchAppsTutorialClicked = { + lastSelectedScreen = SWITCH_APPS_GESTURE + vm.goTo(SWITCH_APPS_GESTURE) + }, onDoneButtonClicked = closeTutorial, lastSelectedScreen, ) @@ -152,5 +162,12 @@ fun TouchpadTutorialScreen( onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, onBack = { vm.goTo(TUTORIAL_SELECTION) }, ) + SWITCH_APPS_GESTURE -> + SwitchAppsGestureTutorialScreen( + switchAppsGestureScreenViewModel, + easterEggGestureViewModel, + onDoneButtonClicked = { vm.goTo(SWITCH_APPS_GESTURE) }, + onBack = { vm.goTo(TUTORIAL_SELECTION) }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt new file mode 100644 index 000000000000..b1e163b55377 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 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.touchpad.tutorial.ui.viewmodel + +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.SwitchAppsGestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class SwitchAppsGestureRecognizerProvider +@Inject +constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : + GestureRecognizerProvider { + + override val recognizer: Flow<GestureRecognizer> = + resources.distanceThreshold().map { + SwitchAppsGestureRecognizer(gestureDistanceThresholdPx = it) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt new file mode 100644 index 000000000000..6593db49745d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 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.touchpad.tutorial.ui.viewmodel + +import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class SwitchAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : + TouchpadTutorialScreenViewModel { + + // TODO: replace with correct markers and resource + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .map { + it to + TutorialAnimationProperties( + progressStartMarker = "drag with gesture", + progressEndMarker = "onPause", + successAnimation = R.raw.trackpad_recent_apps_success, + ) + } + .mapToTutorialState() + + override fun handleEvent(event: MotionEvent): Boolean { + return gestureRecognizer.handleTouchpadMotionEvent(event) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt index c56dcf3bf062..c6d5e7ad0a71 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.StateFlow class TouchpadTutorialViewModel( private val gesturesInteractor: TouchpadGesturesInteractor, - private val logger: InputDeviceTutorialLogger + private val logger: InputDeviceTutorialLogger, ) : ViewModel() { private val _screen = MutableStateFlow(Screen.TUTORIAL_SELECTION) @@ -50,7 +50,7 @@ class TouchpadTutorialViewModel( @Inject constructor( private val gesturesInteractor: TouchpadGesturesInteractor, - private val logger: InputDeviceTutorialLogger + private val logger: InputDeviceTutorialLogger, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -65,4 +65,5 @@ enum class Screen { BACK_GESTURE, HOME_GESTURE, RECENT_APPS_GESTURE, + SWITCH_APPS_GESTURE, } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt index 9cf02f26c9f7..ef147c741bec 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt @@ -21,6 +21,7 @@ import android.content.Context import android.graphics.drawable.Drawable import android.media.AudioManager import androidx.annotation.DrawableRes +import com.android.settingslib.R as SettingsR import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.RingerMode @@ -30,8 +31,10 @@ import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +@SuppressLint("UseCompatLoadingForDrawables") class VolumeDialogSliderIconProvider @Inject constructor( @@ -40,7 +43,30 @@ constructor( private val audioVolumeInteractor: AudioVolumeInteractor, ) { - @SuppressLint("UseCompatLoadingForDrawables") + fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> { + return flow { + val iconRes = + if (isMuted) { + R.drawable.ic_volume_media_bt_mute + } else { + R.drawable.ic_volume_media_bt + } + emit(context.getDrawable(iconRes)!!) + } + } + + fun getCastIcon(isMuted: Boolean): Flow<Drawable> { + return flow { + val iconRes = + if (isMuted) { + SettingsR.drawable.ic_volume_remote_mute + } else { + SettingsR.drawable.ic_volume_remote + } + emit(context.getDrawable(iconRes)!!) + } + } + fun getStreamIcon( stream: Int, level: Int, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index 89dd0352afa7..a752f1f78e74 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -57,12 +57,12 @@ private const val VOLUME_UPDATE_GRACE_PERIOD = 1000 class VolumeDialogSliderViewModel @Inject constructor( + private val sliderType: VolumeDialogSliderType, private val interactor: VolumeDialogSliderInteractor, private val visibilityInteractor: VolumeDialogVisibilityInteractor, @VolumeDialog private val coroutineScope: CoroutineScope, private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider, private val systemClock: SystemClock, - private val sliderType: VolumeDialogSliderType, private val logger: VolumeDialogLogger, ) { @@ -82,14 +82,24 @@ constructor( model .flatMapLatest { streamModel -> with(streamModel) { - volumeDialogSliderIconProvider.getStreamIcon( - stream = stream, - level = level, - levelMin = levelMin, - levelMax = levelMax, - isMuted = muteSupported && muted, - isRoutedToBluetooth = routedToBluetooth, - ) + val isMuted = muteSupported && muted + when (sliderType) { + is VolumeDialogSliderType.Stream -> + volumeDialogSliderIconProvider.getStreamIcon( + stream = sliderType.audioStream, + level = level, + levelMin = levelMin, + levelMax = levelMax, + isMuted = isMuted, + isRoutedToBluetooth = routedToBluetooth, + ) + is VolumeDialogSliderType.RemoteMediaStream -> { + volumeDialogSliderIconProvider.getCastIcon(isMuted) + } + is VolumeDialogSliderType.AudioSharingStream -> { + volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted) + } + } } .map { icon -> streamModel.toStateModel(icon) } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index 411e06ed1339..0c8dc11f4327 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -293,7 +293,7 @@ public class QuickAccessWalletController { intent = getSysUiWalletIntent(); } startQuickAccessViaIntent(intent, hasCard, activityStarter, - animationController); + animationController, mQuickAccessWalletClient.getUser()); }); } @@ -323,7 +323,8 @@ public class QuickAccessWalletController { private void startQuickAccessViaIntent(Intent intent, boolean hasCard, ActivityStarter activityStarter, - ActivityTransitionAnimator.Controller animationController) { + ActivityTransitionAnimator.Controller animationController, + UserHandle user) { if (hasCard) { activityStarter.startActivity(intent, true /* dismissShade */, animationController, true /* showOverLockscreenWhenLocked */); @@ -331,7 +332,9 @@ public class QuickAccessWalletController { activityStarter.postStartActivityDismissingKeyguard( intent, /* delay= */ 0, - animationController); + animationController, + null, + user); } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index 111492c3e227..18e1b6eb323d 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -163,15 +163,13 @@ public class WalletActivity extends ComponentActivity implements if (mKeyguardStateController.isUnlocked()) { mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL); - mActivityStarter.startActivity( - mWalletClient.createWalletIntent(), true); + startWalletActivity(); finish(); } else { mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_SHOW_ALL_BUTTON); mKeyguardDismissUtil.executeWhenUnlocked(() -> { mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL); - mActivityStarter.startActivity( - mWalletClient.createWalletIntent(), true); + startWalletActivity(); finish(); return false; }, false, true); @@ -193,6 +191,11 @@ public class WalletActivity extends ComponentActivity implements }); } + private void startWalletActivity() { + mActivityStarter.startActivity(mWalletClient.createWalletIntent(), true, + null, true, mWalletClient.getUser()); + } + @Override protected void onStart() { super.onStart(); diff --git a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt b/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt deleted file mode 100644 index 8b6c8601f5d2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt +++ /dev/null @@ -1,32 +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.systemui.window.flag - -import com.android.systemui.Flags - -/** - * Flag that controls whether the background surface is blurred or not while on the - * lockscreen/shade/bouncer. This makes the background of scrim, bouncer and few other opaque - * surfaces transparent so that we can see the blur effect on the background surface (wallpaper). - */ -object WindowBlurFlag { - /** Whether the blur is enabled or not */ - @JvmStatic - val isEnabled - // Add flags here that require scrims/background surfaces to be transparent. - get() = Flags.notificationShadeBlur() || Flags.bouncerUiRevamp() -} diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt index 2491ca7565c7..d2069cfdfdc6 100644 --- a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt @@ -19,12 +19,12 @@ package com.android.systemui.window.ui import android.util.Log import android.view.Choreographer import android.view.Choreographer.FrameCallback +import com.android.systemui.Flags import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.viewModel import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.statusbar.BlurUtils -import com.android.systemui.window.flag.WindowBlurFlag import com.android.systemui.window.ui.viewmodel.WindowRootViewModel import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.filter @@ -43,7 +43,7 @@ object WindowRootViewBinder { blurUtils: BlurUtils?, choreographer: Choreographer?, ) { - if (!WindowBlurFlag.isEnabled) return + if (!Flags.bouncerUiRevamp()) return if (blurUtils == null || choreographer == null) return view.repeatWhenAttached { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index a17f100904be..e5376d26840d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -88,8 +88,13 @@ public class RecordingControllerTest extends SysuiTestCase { private ScreenRecordPermissionDialogDelegate.Factory mScreenRecordPermissionDialogDelegateFactory; @Mock + private ScreenRecordPermissionViewBinder.Factory + mScreenRecordPermissionViewBinderFactory; + @Mock private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate; @Mock + private ScreenRecordPermissionViewBinder mScreenRecordPermissionViewBinder; + @Mock private SystemUIDialog mScreenRecordSystemUIDialog; private RecordingController mController; @@ -106,6 +111,8 @@ public class RecordingControllerTest extends SysuiTestCase { .thenReturn(mScreenCaptureDisabledDialog); when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any())) .thenReturn(mScreenRecordPermissionDialogDelegate); + when(mScreenRecordPermissionViewBinderFactory.create(any(), anyInt(), any(), any())) + .thenReturn(mScreenRecordPermissionViewBinder); when(mScreenRecordPermissionDialogDelegate.createDialog()) .thenReturn(mScreenRecordSystemUIDialog); mController = new RecordingController( @@ -116,7 +123,8 @@ public class RecordingControllerTest extends SysuiTestCase { new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")), mMediaProjectionMetricsLogger, mScreenCaptureDisabledDialogDelegate, - mScreenRecordPermissionDialogDelegateFactory + mScreenRecordPermissionDialogDelegateFactory, + mScreenRecordPermissionViewBinderFactory ); mController.addCallback(mCallback); } @@ -238,6 +246,26 @@ public class RecordingControllerTest extends SysuiTestCase { } @Test + public void testCreateScreenRecordPermissionViewBinder() { + ScreenRecordPermissionViewBinder viewBinder = + mController.createScreenRecordPermissionViewBinder( + /* onStartRecordingClicked= */ null); + assertThat(viewBinder).isEqualTo(mScreenRecordPermissionViewBinder); + } + + @Test + public void testScreenCapturingAllowed_returnsFalseIsScreenCaptureDisabled() { + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + assertFalse(mController.isScreenCaptureDisabled()); + } + + @Test + public void testScreenCapturingNotAllowed_returnsTrueIsScreenCaptureDisabled() { + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); + assertTrue(mController.isScreenCaptureDisabled()); + } + + @Test public void testScreenCapturingAllowed_logsProjectionInitiated() { when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 72d1db3affe8..281ce16b539f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -22,6 +22,7 @@ import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; +import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; @@ -43,6 +44,8 @@ import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.os.Bundle; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; @@ -54,6 +57,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -280,6 +285,40 @@ public class NotificationEntryTest extends SysuiTestCase { } @Test + @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME}) + public void isPromotedOngoing_noFlagOnNotif_false() { + mEntry.getSbn().getNotification().flags &= ~FLAG_PROMOTED_ONGOING; + + assertFalse(mEntry.isPromotedOngoing()); + } + + @Test + @DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME}) + public void isPromotedOngoing_statusBarNotifChipsFlagAndUiFlagOff_false() { + mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING; + + assertFalse(mEntry.isPromotedOngoing()); + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME) + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + public void isPromotedOngoing_uiFlagOnAndNotifHasFlag_true() { + mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING; + + assertTrue(mEntry.isPromotedOngoing()); + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + @DisableFlags(PromotedNotificationUi.FLAG_NAME) + public void isPromotedOngoing_statusBarNotifChipsFlagOnAndNotifHasFlag_true() { + mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING; + + assertTrue(mEntry.isPromotedOngoing()); + } + + @Test public void testIsNotificationVisibilityPrivate_true() { assertTrue(mEntry.isNotificationVisibilityPrivate()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java index 733e2edaec84..8e97c86ba507 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.wallet.controller; import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -35,6 +36,7 @@ import static org.mockito.Mockito.when; import android.app.PendingIntent; import android.app.role.RoleManager; import android.content.Intent; +import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; import android.service.quickaccesswallet.GetWalletCardsRequest; import android.service.quickaccesswallet.QuickAccessWalletClient; @@ -59,7 +61,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; import java.util.List; @@ -98,6 +99,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); + when(mQuickAccessWalletClient.getUser()).thenReturn(UserHandle.of(0)); mClock.setElapsedRealtime(100L); doAnswer(invocation -> { @@ -269,7 +271,8 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() { mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false); verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0), - any(ActivityTransitionAnimator.Controller.class)); + any(ActivityTransitionAnimator.Controller.class), eq(null), + eq(UserHandle.of(0))); Intent intent = mIntentCaptor.getValue(); assertEquals(intent.getAction(), Intent.ACTION_VIEW); assertEquals( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt index 82a5311269f0..56a7b4db5455 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt @@ -18,10 +18,12 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope val Kosmos.keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor by Kosmos.Fixture { KeyguardDismissTransitionInteractor( + scope = testScope, repository = keyguardTransitionRepository, fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor, fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index abbfa93edd17..1c0f97d294df 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -56,9 +56,11 @@ val Kosmos.keyguardRootViewModel by Fixture { aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, + aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel, dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, + dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel, dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel, dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt index 3c37101cfe66..13cbddff8803 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate +import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import javax.inject.Provider val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by @@ -28,5 +29,6 @@ val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by qsTileIntentUserInputHandler, Provider { modesDialogDelegate }.get(), zenModeInteractor, + modesDialogEventLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 60e092c9709b..8461da77796d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -28,9 +28,11 @@ import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.dozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel @@ -78,9 +80,11 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, + aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel, dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, + dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, goneToAodTransitionViewModel = goneToAodTransitionViewModel, goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt index 766b280334c8..f4e74fe0e6bb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.shared.model import android.app.PendingIntent import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel /** Helper for building [OngoingCallModel.InCall] instances in tests. */ fun inCallModel( @@ -25,4 +26,5 @@ fun inCallModel( notificationIcon: StatusBarIconView? = null, intent: PendingIntent? = null, notificationKey: String = "test", -) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey) + promotedContent: PromotedNotificationContentModel? = null, +) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey, promotedContent) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt index db7e31bb2cb6..f8bf3c3fbbd9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.tableLogBufferFactory import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -39,6 +40,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by Kosmos.Fixture { HomeStatusBarViewModelImpl( testableContext.displayId, + tableLogBufferFactory, homeStatusBarInteractor, homeStatusBarIconBlockListInteractor, lightsOutInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt index 6c98d19db5d7..ef043e0177a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy.ui.dialog +import android.content.mockedContext import com.android.systemui.animation.dialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.mainCoroutineContext @@ -30,6 +31,7 @@ val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate> var Kosmos.modesDialogDelegate: ModesDialogDelegate by Kosmos.Fixture { ModesDialogDelegate( + mockedContext, systemUIDialogFactory, dialogTransitionAnimator, activityStarter, diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt index 3ec3e3ce2946..27223d8b72ff 100644 --- a/ravenwood/texts/ravenwood-standard-options.txt +++ b/ravenwood/texts/ravenwood-standard-options.txt @@ -5,6 +5,8 @@ # Keep all classes / methods / fields, but make the methods throw. --default-throw +--delete-finals + # Uncomment below lines to enable each feature. #--default-method-call-hook diff --git a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt index 001943c18d6b..9c46a1646560 100644 --- a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt +++ b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt @@ -2,6 +2,8 @@ --debug +--delete-finals + # Uncomment below lines to enable each feature. #--default-method-call-hook diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index cc704b2b32ed..985947575a86 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -411,6 +411,8 @@ class HostStubGen(val options: HostStubGenOptions) { stats = stats, enablePreTrace = options.enablePreTrace.get, enablePostTrace = options.enablePostTrace.get, + deleteClassFinals = options.deleteFinals.get, + deleteMethodFinals = options.deleteFinals.get, ) outVisitor = BaseAdapter.getVisitor( classInternalName, classes, outVisitor, filter, diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 55e853e3e2fb..ae9276f711e1 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -106,6 +106,8 @@ class HostStubGenOptions( var cleanUpOnError: SetOnce<Boolean> = SetOnce(false), + var deleteFinals: SetOnce<Boolean> = SetOnce(false), + var enableClassChecker: SetOnce<Boolean> = SetOnce(false), var enablePreTrace: SetOnce<Boolean> = SetOnce(false), var enablePostTrace: SetOnce<Boolean> = SetOnce(false), @@ -218,6 +220,8 @@ class HostStubGenOptions( "--gen-keep-all-file" -> ret.inputJarAsKeepAllFile.set(nextArg()) + "--delete-finals" -> ret.deleteFinals.set(true) + // Following options are for debugging. "--enable-class-checker" -> ret.enableClassChecker.set(true) "--no-class-checker" -> ret.enableClassChecker.set(false) @@ -293,6 +297,7 @@ class HostStubGenOptions( defaultMethodCallHook=$defaultMethodCallHook, policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()}, defaultPolicy=$defaultPolicy, + deleteFinals=$deleteFinals, cleanUpOnError=$cleanUpOnError, enableClassChecker=$enableClassChecker, enablePreTrace=$enablePreTrace, diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt index 261ef59c45c7..a08d1d605949 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt @@ -50,7 +50,13 @@ abstract class BaseAdapter( val errors: HostStubGenErrors, val stats: HostStubGenStats?, val enablePreTrace: Boolean, - val enablePostTrace: Boolean + val enablePostTrace: Boolean, + val deleteClassFinals: Boolean, + val deleteMethodFinals: Boolean, + // We don't remove finals from fields, because final fields have a stronger memory + // guarantee than non-final fields, see: + // https://docs.oracle.com/javase/specs/jls/se22/html/jls-17.html#jls-17.5 + // i.e. changing a final field to non-final _could_ result in different behavior. ) protected lateinit var currentPackageName: String @@ -58,14 +64,33 @@ abstract class BaseAdapter( protected var redirectionClass: String? = null protected lateinit var classPolicy: FilterPolicyWithReason + private fun isEnum(access: Int): Boolean { + return (access and Opcodes.ACC_ENUM) != 0 + } + + protected fun modifyClassAccess(access: Int): Int { + if (options.deleteClassFinals && !isEnum(access)) { + return access and Opcodes.ACC_FINAL.inv() + } + return access + } + + protected fun modifyMethodAccess(access: Int): Int { + if (options.deleteMethodFinals) { + return access and Opcodes.ACC_FINAL.inv() + } + return access + } + override fun visit( version: Int, - access: Int, + origAccess: Int, name: String, signature: String?, superName: String?, interfaces: Array<String>, ) { + val access = modifyClassAccess(origAccess) super.visit(version, access, name, signature, superName, interfaces) currentClassName = name currentPackageName = getPackageNameFromFullClassName(name) @@ -130,13 +155,14 @@ abstract class BaseAdapter( } } - override fun visitMethod( - access: Int, + final override fun visitMethod( + origAccess: Int, name: String, descriptor: String, signature: String?, exceptions: Array<String>?, ): MethodVisitor? { + val access = modifyMethodAccess(origAccess) if (skipMemberModificationNestCount > 0) { return super.visitMethod(access, name, descriptor, signature, exceptions) } @@ -176,6 +202,7 @@ abstract class BaseAdapter( if (newAccess == NOT_COMPATIBLE) { return null } + newAccess = modifyMethodAccess(newAccess) log.v( "Emitting %s.%s%s as %s %s", currentClassName, name, descriptor, diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 567a69e43b58..70e7d46bb6cd 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -51,12 +51,13 @@ class ImplGeneratingAdapter( override fun visit( version: Int, - access: Int, + origAccess: Int, name: String, signature: String?, superName: String?, interfaces: Array<String> ) { + val access = modifyClassAccess(origAccess) super.visit(version, access, name, signature, superName, interfaces) classLoadHooks = filter.getClassLoadHooks(currentClassName) diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt index 5e5ca62b49f0..b009b0957919 100644 --- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -7,6 +7,8 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 +Constant pool: +{ public abstract java.lang.String value(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT @@ -30,6 +32,8 @@ public interface android.hosttest.annotation.HostSideTestIgnore extends java.lan this_class: #x // android/hosttest/annotation/HostSideTestIgnore super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestIgnore.java" RuntimeVisibleAnnotations: @@ -50,6 +54,8 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang. this_class: #x // android/hosttest/annotation/HostSideTestKeep super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestKeep.java" RuntimeVisibleAnnotations: @@ -70,6 +76,8 @@ public interface android.hosttest.annotation.HostSideTestRedirect extends java.l this_class: #x // android/hosttest/annotation/HostSideTestRedirect super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestRedirect.java" RuntimeVisibleAnnotations: @@ -90,6 +98,8 @@ public interface android.hosttest.annotation.HostSideTestRedirectionClass extend this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 +Constant pool: +{ public abstract java.lang.String value(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT @@ -113,6 +123,8 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan this_class: #x // android/hosttest/annotation/HostSideTestRemove super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestRemove.java" RuntimeVisibleAnnotations: @@ -133,6 +145,8 @@ public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep e this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestStaticInitializerKeep.java" RuntimeVisibleAnnotations: @@ -153,6 +167,8 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java this_class: #x // android/hosttest/annotation/HostSideTestSubstitute super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 +Constant pool: +{ public abstract java.lang.String suffix(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT @@ -176,6 +192,8 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang this_class: #x // android/hosttest/annotation/HostSideTestThrow super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestThrow.java" RuntimeVisibleAnnotations: @@ -196,6 +214,8 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestWholeClassKeep.java" RuntimeVisibleAnnotations: @@ -216,6 +236,8 @@ public interface android.hosttest.annotation.tests.HostSideTestSuppress extends this_class: #x // android/hosttest/annotation/tests/HostSideTestSuppress super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestSuppress.java" RuntimeVisibleAnnotations: @@ -232,6 +254,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Pro this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -273,6 +297,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -314,6 +340,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 3 +Constant pool: +{ } SourceFile: "IPretendingAidl.java" NestMembers: @@ -331,6 +359,8 @@ public class com.android.hoststubgen.test.tinyframework.R$Nested this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 3 +Constant pool: +{ public static int[] ARRAY; descriptor: [I flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -376,6 +406,8 @@ public class com.android.hoststubgen.test.tinyframework.R this_class: #x // com/android/hoststubgen/test/tinyframework/R super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.R(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -396,13 +428,15 @@ InnerClasses: public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class Compiled from "TinyFrameworkAnnotations.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations +public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations minor version: 0 major version: 65 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER + flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 9, attributes: 2 +Constant pool: +{ public int keep; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -433,9 +467,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations x: #x() android.hosttest.annotation.HostSideTestKeep - public int addOne(int); + public final int addOne(int); descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=2, locals=2, args_size=2 x: iload_1 @@ -505,18 +539,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 4 1 value I - public static native int nativeAddThree(int); + public static final native int nativeAddThree(int); descriptor: (I)I - flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE RuntimeInvisibleAnnotations: x: #x(#x=s#x) android.hosttest.annotation.HostSideTestSubstitute( suffix="_host" ) - private static int nativeAddThree_host(int); + private static final int nativeAddThree_host(int); descriptor: (I)I - flags: (0x000a) ACC_PRIVATE, ACC_STATIC + flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL Code: stack=2, locals=1, args_size=1 x: iload_0 @@ -578,6 +612,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 3, attributes: 2 +Constant pool: +{ public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; descriptor: Ljava/util/Set; flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL @@ -640,6 +676,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAn this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 6, attributes: 2 +Constant pool: +{ public int keep; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -764,6 +802,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 2, attributes: 2 +Constant pool: +{ public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -818,6 +858,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 2, attributes: 2 +Constant pool: +{ public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -878,6 +920,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumC this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex super_class: #x // java/lang/Enum interfaces: 0, fields: 6, methods: 7, attributes: 3 +Constant pool: +{ public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED; descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM @@ -1081,6 +1125,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumS this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple super_class: #x // java/lang/Enum interfaces: 0, fields: 3, methods: 5, attributes: 3 +Constant pool: +{ public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT; descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM @@ -1202,6 +1248,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1256,6 +1304,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 17, attributes: 1 +Constant pool: +{ public int stub; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -1507,6 +1557,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nes this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 8, attributes: 5 +Constant pool: +{ public final java.util.function.Supplier<java.lang.Integer> mSupplier; descriptor: Ljava/util/function/Supplier; flags: (0x0011) ACC_PUBLIC, ACC_FINAL @@ -1661,6 +1713,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 8, attributes: 5 +Constant pool: +{ public final java.util.function.Supplier<java.lang.Integer> mSupplier; descriptor: Ljava/util/function/Supplier; flags: (0x0011) ACC_PUBLIC, ACC_FINAL @@ -1816,6 +1870,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 3, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1873,6 +1929,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 5, attributes: 5 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1984,6 +2042,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 14, attributes: 2 +Constant pool: +{ int value; descriptor: I flags: (0x0000) @@ -2157,6 +2217,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 7, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2263,6 +2325,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses); descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V flags: (0x0000) @@ -2321,6 +2385,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2(); descriptor: ()V flags: (0x0000) @@ -2375,6 +2441,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses); descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V flags: (0x0000) @@ -2433,6 +2501,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4(); descriptor: ()V flags: (0x0000) @@ -2487,6 +2557,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 1, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2521,6 +2593,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 1, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2558,6 +2632,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1(); descriptor: ()V flags: (0x0000) @@ -2613,6 +2689,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 1, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2647,6 +2725,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2694,6 +2774,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass interfaces: 0, fields: 0, methods: 1, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int); descriptor: (I)V flags: (0x0001) ACC_PUBLIC @@ -2723,6 +2805,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 4, attributes: 4 +Constant pool: +{ public final java.util.function.Supplier<java.lang.Integer> mSupplier; descriptor: Ljava/util/function/Supplier; flags: (0x0011) ACC_PUBLIC, ACC_FINAL @@ -2827,6 +2911,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedi this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2869,6 +2955,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClas this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2911,6 +2999,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 2 +Constant pool: +{ private final int mValue; descriptor: I flags: (0x0012) ACC_PRIVATE, ACC_FINAL @@ -2958,6 +3048,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.A this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.A(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2981,6 +3073,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.B this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.B(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3004,6 +3098,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.sub.A(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3027,6 +3123,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.sub.B(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3050,6 +3148,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C1 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1 super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.C1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3073,6 +3173,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.C2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3096,6 +3198,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.C3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3119,6 +3223,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.CA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3142,6 +3248,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CB this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.CB(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3165,6 +3273,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3188,6 +3298,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3211,6 +3323,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3234,6 +3348,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3257,6 +3373,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3280,6 +3398,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3303,6 +3423,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3326,6 +3448,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3349,6 +3473,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3372,6 +3498,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3395,6 +3523,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3418,6 +3548,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3441,6 +3573,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1 super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3464,6 +3598,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3 super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3487,6 +3623,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3510,6 +3648,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3533,6 +3673,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_None this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3556,6 +3698,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1 super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "I1.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class @@ -3567,6 +3711,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 exte this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "I2.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class @@ -3578,6 +3724,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 exte this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "I3.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class @@ -3589,6 +3737,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "IA.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class @@ -3600,6 +3750,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "IB.java" ## Class: com/supported/UnsupportedClass.class @@ -3611,6 +3763,8 @@ public class com.supported.UnsupportedClass this_class: #x // com/supported/UnsupportedClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 2 +Constant pool: +{ private final int mValue; descriptor: I flags: (0x0012) ACC_PRIVATE, ACC_FINAL @@ -3658,6 +3812,8 @@ public class com.unsupported.UnsupportedClass this_class: #x // com/unsupported/UnsupportedClass super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.unsupported.UnsupportedClass(int); descriptor: (I)V flags: (0x0001) ACC_PUBLIC diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 103e152c7e39..ad413425801b 100644 --- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -7,6 +7,8 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 +Constant pool: +{ public abstract java.lang.String value(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT @@ -30,6 +32,8 @@ public interface android.hosttest.annotation.HostSideTestIgnore extends java.lan this_class: #x // android/hosttest/annotation/HostSideTestIgnore super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestIgnore.java" RuntimeVisibleAnnotations: @@ -50,6 +54,8 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang. this_class: #x // android/hosttest/annotation/HostSideTestKeep super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestKeep.java" RuntimeVisibleAnnotations: @@ -70,6 +76,8 @@ public interface android.hosttest.annotation.HostSideTestRedirect extends java.l this_class: #x // android/hosttest/annotation/HostSideTestRedirect super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestRedirect.java" RuntimeVisibleAnnotations: @@ -90,6 +98,8 @@ public interface android.hosttest.annotation.HostSideTestRedirectionClass extend this_class: #x // android/hosttest/annotation/HostSideTestRedirectionClass super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 +Constant pool: +{ public abstract java.lang.String value(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT @@ -113,6 +123,8 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan this_class: #x // android/hosttest/annotation/HostSideTestRemove super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestRemove.java" RuntimeVisibleAnnotations: @@ -133,6 +145,8 @@ public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep e this_class: #x // android/hosttest/annotation/HostSideTestStaticInitializerKeep super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestStaticInitializerKeep.java" RuntimeVisibleAnnotations: @@ -153,6 +167,8 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java this_class: #x // android/hosttest/annotation/HostSideTestSubstitute super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 2 +Constant pool: +{ public abstract java.lang.String suffix(); descriptor: ()Ljava/lang/String; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT @@ -176,6 +192,8 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang this_class: #x // android/hosttest/annotation/HostSideTestThrow super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestThrow.java" RuntimeVisibleAnnotations: @@ -196,6 +214,8 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestWholeClassKeep.java" RuntimeVisibleAnnotations: @@ -216,6 +236,8 @@ public interface android.hosttest.annotation.tests.HostSideTestSuppress extends this_class: #x // android/hosttest/annotation/tests/HostSideTestSuppress super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 2 +Constant pool: +{ } SourceFile: "HostSideTestSuppress.java" RuntimeVisibleAnnotations: @@ -232,6 +254,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Pro this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -273,6 +297,8 @@ public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -314,6 +340,8 @@ public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 3 +Constant pool: +{ } SourceFile: "IPretendingAidl.java" NestMembers: @@ -331,6 +359,8 @@ public class com.android.hoststubgen.test.tinyframework.R$Nested this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 3 +Constant pool: +{ public static int[] ARRAY; descriptor: [I flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -376,6 +406,8 @@ public class com.android.hoststubgen.test.tinyframework.R this_class: #x // com/android/hoststubgen/test/tinyframework/R super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.R(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -396,13 +428,15 @@ InnerClasses: public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class Compiled from "TinyFrameworkAnnotations.java" -public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations +public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations minor version: 0 major version: 61 - flags: (0x0021) ACC_PUBLIC, ACC_SUPER + flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 9, attributes: 2 +Constant pool: +{ public int keep; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -433,9 +467,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations x: #x() android.hosttest.annotation.HostSideTestKeep - public int addOne(int); + public final int addOne(int); descriptor: (I)I - flags: (0x0001) ACC_PUBLIC + flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=2, locals=2, args_size=2 x: iload_1 @@ -505,18 +539,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations; 0 4 1 value I - public static native int nativeAddThree(int); + public static final native int nativeAddThree(int); descriptor: (I)I - flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE RuntimeInvisibleAnnotations: x: #x(#x=s#x) android.hosttest.annotation.HostSideTestSubstitute( suffix="_host" ) - private static int nativeAddThree_host(int); + private static final int nativeAddThree_host(int); descriptor: (I)I - flags: (0x000a) ACC_PRIVATE, ACC_STATIC + flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL Code: stack=2, locals=1, args_size=1 x: iload_0 @@ -578,6 +612,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 3, attributes: 2 +Constant pool: +{ public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; descriptor: Ljava/util/Set; flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL @@ -640,6 +676,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAn this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 6, attributes: 2 +Constant pool: +{ public int keep; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -764,6 +802,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 2, attributes: 2 +Constant pool: +{ public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -818,6 +858,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 2, attributes: 2 +Constant pool: +{ public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -878,6 +920,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumC this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex super_class: #x // java/lang/Enum interfaces: 0, fields: 6, methods: 7, attributes: 3 +Constant pool: +{ public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED; descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM @@ -1081,6 +1125,8 @@ public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumS this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple super_class: #x // java/lang/Enum interfaces: 0, fields: 3, methods: 5, attributes: 3 +Constant pool: +{ public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT; descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM @@ -1202,6 +1248,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1256,6 +1304,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 17, attributes: 1 +Constant pool: +{ public int stub; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -1507,6 +1557,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nes this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 8, attributes: 5 +Constant pool: +{ public final java.util.function.Supplier<java.lang.Integer> mSupplier; descriptor: Ljava/util/function/Supplier; flags: (0x0011) ACC_PUBLIC, ACC_FINAL @@ -1661,6 +1713,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 8, attributes: 5 +Constant pool: +{ public final java.util.function.Supplier<java.lang.Integer> mSupplier; descriptor: Ljava/util/function/Supplier; flags: (0x0011) ACC_PUBLIC, ACC_FINAL @@ -1816,6 +1870,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 3, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1873,6 +1929,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 5, attributes: 5 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1984,6 +2042,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 14, attributes: 2 +Constant pool: +{ int value; descriptor: I flags: (0x0000) @@ -2157,6 +2217,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 7, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2263,6 +2325,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 super_class: #x // java/lang/Object interfaces: 1, fields: 1, methods: 3, attributes: 5 +Constant pool: +{ final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0; descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC @@ -2328,6 +2392,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2(); descriptor: ()V flags: (0x0000) @@ -2382,6 +2448,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 super_class: #x // java/lang/Object interfaces: 1, fields: 1, methods: 3, attributes: 5 +Constant pool: +{ final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0; descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC @@ -2447,6 +2515,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4(); descriptor: ()V flags: (0x0000) @@ -2501,6 +2571,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 1, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2535,6 +2607,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 1, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2579,6 +2653,8 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 3, attributes: 5 +Constant pool: +{ com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1(); descriptor: ()V flags: (0x0000) @@ -2634,6 +2710,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 1, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2668,6 +2746,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 3 +Constant pool: +{ public int value; descriptor: I flags: (0x0001) ACC_PUBLIC @@ -2715,6 +2795,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass interfaces: 0, fields: 0, methods: 1, attributes: 3 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int); descriptor: (I)V flags: (0x0001) ACC_PUBLIC @@ -2744,6 +2826,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses super_class: #x // java/lang/Object interfaces: 0, fields: 2, methods: 4, attributes: 4 +Constant pool: +{ public final java.util.function.Supplier<java.lang.Integer> mSupplier; descriptor: Ljava/util/function/Supplier; flags: (0x0011) ACC_PUBLIC, ACC_FINAL @@ -2848,6 +2932,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedi this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2890,6 +2976,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClas this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2932,6 +3020,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 2 +Constant pool: +{ private final int mValue; descriptor: I flags: (0x0012) ACC_PRIVATE, ACC_FINAL @@ -2979,6 +3069,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.A this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.A(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3002,6 +3094,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.B this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.B(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3025,6 +3119,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.sub.A(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3048,6 +3144,8 @@ public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.packagetest.sub.B(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3071,6 +3169,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C1 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1 super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.C1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3094,6 +3194,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.C2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3117,6 +3219,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.C3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3140,6 +3244,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.CA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3163,6 +3269,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.CB this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.CB(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3186,6 +3294,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C1 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3209,6 +3319,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C2 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3232,6 +3344,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3 super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/C3 interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3255,6 +3369,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CA interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3278,6 +3394,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB ex this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3301,6 +3419,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA super_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/CB interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3324,6 +3444,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3347,6 +3469,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3370,6 +3494,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3393,6 +3519,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3416,6 +3544,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3439,6 +3569,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3462,6 +3594,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1 super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3485,6 +3619,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3 super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3508,6 +3644,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB im this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3531,6 +3669,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA super_class: #x // java/lang/Object interfaces: 2, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3554,6 +3694,8 @@ public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_None this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 1, attributes: 1 +Constant pool: +{ public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -3577,6 +3719,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1 this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I1 super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "I1.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class @@ -3588,6 +3732,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 exte this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I2 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "I2.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class @@ -3599,6 +3745,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 exte this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/I3 super_class: #x // java/lang/Object interfaces: 1, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "I3.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class @@ -3610,6 +3758,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IA super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "IA.java" ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class @@ -3621,6 +3771,8 @@ public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB this_class: #x // com/android/hoststubgen/test/tinyframework/subclasstest/IB super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 0, attributes: 1 +Constant pool: +{ } SourceFile: "IB.java" ## Class: com/supported/UnsupportedClass.class @@ -3632,6 +3784,8 @@ public class com.supported.UnsupportedClass this_class: #x // com/supported/UnsupportedClass super_class: #x // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 2 +Constant pool: +{ private final int mValue; descriptor: I flags: (0x0012) ACC_PRIVATE, ACC_FINAL @@ -3679,6 +3833,8 @@ public class com.unsupported.UnsupportedClass this_class: #x // com/unsupported/UnsupportedClass super_class: #x // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 2 +Constant pool: +{ public com.unsupported.UnsupportedClass(int); descriptor: (I)V flags: (0x0001) ACC_PUBLIC diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java index 3415deb957ed..674937d15424 100644 --- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java @@ -28,7 +28,7 @@ import android.hosttest.annotation.HostSideTestThrow; @HostSideTestKeep @HostSideTestClassLoadHook( "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded") -public class TinyFrameworkAnnotations { +public final class TinyFrameworkAnnotations { @HostSideTestKeep public TinyFrameworkAnnotations() { } @@ -42,7 +42,7 @@ public class TinyFrameworkAnnotations { public int remove; @HostSideTestKeep - public int addOne(int value) { + public final int addOne(int value) { return value + 1; } @@ -61,10 +61,10 @@ public class TinyFrameworkAnnotations { } @HostSideTestSubstitute(suffix = "_host") - public static native int nativeAddThree(int value); + public final static native int nativeAddThree(int value); // This method is private, but at runtime, it'll inherit the visibility of the original method - private static int nativeAddThree_host(int value) { + private final static int nativeAddThree_host(int value) { return value + 3; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 79888b051c54..70c4c1311fc9 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -21,14 +21,14 @@ import static android.view.MotionEvent.ACTION_SCROLL; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; + import android.accessibilityservice.AccessibilityTrace; import android.annotation.MainThread; import android.annotation.NonNull; import android.content.Context; import android.graphics.Region; import android.hardware.input.InputManager; -import android.hardware.input.KeyGestureEvent; -import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; @@ -46,15 +46,13 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.accessibility.AccessibilityEvent; -import androidx.annotation.Nullable; - import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; -import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; +import com.android.server.accessibility.magnification.MagnificationKeyHandler; import com.android.server.accessibility.magnification.MouseEventHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationPromptController; @@ -209,6 +207,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private MouseKeysInterceptor mMouseKeysInterceptor; + private MagnificationKeyHandler mMagnificationKeyHandler; + private boolean mInstalled; private int mUserId; @@ -235,74 +235,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ private MotionEvent mLastActiveDeviceMotionEvent = null; - private boolean mKeyGestureEventHandlerInstalled = false; - private InputManager.KeyGestureEventHandler mKeyGestureEventHandler = - new InputManager.KeyGestureEventHandler() { - @Override - public boolean handleKeyGestureEvent( - @NonNull KeyGestureEvent event, - @Nullable IBinder focusedToken) { - final boolean complete = - event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE - && !event.isCancelled(); - - // TODO(b/355499907): Receive and handle held key gestures, which can be used - // for continuous scaling and panning. In addition, handle multiple pan gestures - // at the same time (e.g. user may try to pan diagonally) reasonably, including - // decreasing diagonal movement by sqrt(2) to make it appear the same speed - // as non-diagonal movement. - - if (!complete) { - return false; - } - - final int gestureType = event.getKeyGestureType(); - final int displayId = isDisplayIdValid(event.getDisplayId()) - ? event.getDisplayId() : Display.DEFAULT_DISPLAY; - - switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN: - mAms.getMagnificationController().scaleMagnificationByStep( - displayId, MagnificationController.ZOOM_DIRECTION_IN); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT: - mAms.getMagnificationController().scaleMagnificationByStep( - displayId, MagnificationController.ZOOM_DIRECTION_OUT); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_LEFT); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_RIGHT); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_UP); - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN: - mAms.getMagnificationController().panMagnificationByStep( - displayId, MagnificationController.PAN_DIRECTION_DOWN); - return true; - } - return false; - } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - return switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN -> true; - default -> false; - }; - } - }; - private static MotionEvent cancelMotion(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT @@ -787,20 +719,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo }); } - if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0) - || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) - || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { + if (isAnyMagnificationEnabled()) { final MagnificationGestureHandler magnificationGestureHandler = createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); - - if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures() - && !mKeyGestureEventHandlerInstalled) { - mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler); - mKeyGestureEventHandlerInstalled = true; - } } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { @@ -817,6 +740,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { + // mKeyboardInterceptor does not forward KeyEvents to other EventStreamTransformations, + // so it must be the last EventStreamTransformation for key events in the list. mKeyboardInterceptor = new KeyboardInterceptor(mAms, LocalServices.getService(WindowManagerPolicy.class)); // Since the display id of KeyEvent always would be -1 and it would be dispatched to @@ -832,6 +757,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo Display.DEFAULT_DISPLAY); addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor); } + + if (enableTalkbackAndMagnifierKeyGestures() && isAnyMagnificationEnabled()) { + mMagnificationKeyHandler = new MagnificationKeyHandler( + mAms.getMagnificationController()); + addFirstEventHandler(Display.DEFAULT_DISPLAY, mMagnificationKeyHandler); + } + } + + private boolean isAnyMagnificationEnabled() { + return (mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 + || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0); } /** @@ -921,9 +859,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mMouseKeysInterceptor = null; } - if (mKeyGestureEventHandlerInstalled) { - mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler); - mKeyGestureEventHandlerInstalled = false; + if (mMagnificationKeyHandler != null) { + mMagnificationKeyHandler.onDestroy(); + mMagnificationKeyHandler = null; } } @@ -1365,6 +1303,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo joiner.add("AutoclickController"); } else if (next instanceof MotionEventInjector) { joiner.add("MotionEventInjector"); + } else if (next instanceof MagnificationKeyHandler) { + joiner.add("MagnificationKeyHandler"); } next = next.getNext(); } diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java index b94fa2f59162..8b758d29a2ac 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java @@ -39,6 +39,8 @@ import android.view.MotionEvent.PointerProperties; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import androidx.annotation.VisibleForTesting; + /** * Implements "Automatically click on mouse stop" feature. * @@ -69,10 +71,10 @@ public class AutoclickController extends BaseEventStreamTransformation { private final int mUserId; // Lazily created on the first mouse motion event. - private ClickScheduler mClickScheduler; - private AutoclickSettingsObserver mAutoclickSettingsObserver; - private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; - private AutoclickIndicatorView mAutoclickIndicatorView; + @VisibleForTesting ClickScheduler mClickScheduler; + @VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver; + @VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; + @VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView; private WindowManager mWindowManager; public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { @@ -360,7 +362,8 @@ public class AutoclickController extends BaseEventStreamTransformation { * moving. The click is first scheduled when a mouse movement is detected, and then further * delayed on every sufficient mouse movement. */ - final private class ClickScheduler implements Runnable { + @VisibleForTesting + final class ClickScheduler implements Runnable { /** * Minimal distance pointer has to move relative to anchor in order for movement not to be * discarded as noise. Anchor is the position of the last MOVE event that was not considered @@ -474,6 +477,11 @@ public class AutoclickController extends BaseEventStreamTransformation { } } + @VisibleForTesting + int getDelayForTesting() { + return mDelay; + } + /** * Updates the time at which click sequence should occur. * diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java index bf5015176f8c..f87dcdb200bb 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java @@ -28,6 +28,8 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.animation.LinearInterpolator; +import androidx.annotation.VisibleForTesting; + // A visual indicator for the autoclick feature. public class AutoclickIndicatorView extends View { private static final String TAG = AutoclickIndicatorView.class.getSimpleName(); @@ -37,7 +39,7 @@ public class AutoclickIndicatorView extends View { static final int MINIMAL_ANIMATION_DURATION = 50; - private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; + private int mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; private final Paint mPaint; @@ -112,6 +114,11 @@ public class AutoclickIndicatorView extends View { mRadius = radius; } + @VisibleForTesting + int getRadiusForTesting() { + return mRadius; + } + public void redrawIndicator() { showIndicator = true; invalidate(); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 0cbbf6da022b..2c106d31ae59 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -16,8 +16,6 @@ package com.android.server.accessibility.gestures; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_ENTER; @@ -86,8 +84,6 @@ import java.util.List; public class TouchExplorer extends BaseEventStreamTransformation implements GestureManifold.Listener { - private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER; - // Tag for logging received events. private static final String LOG_TAG = "TouchExplorer"; @@ -261,10 +257,6 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS, - "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); - } if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { super.onMotionEvent(event, rawEvent, policyFlags); return; @@ -323,9 +315,8 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onAccessibilityEvent(AccessibilityEvent event) { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent", - LOGGING_FLAGS, "event=" + event); + if (DEBUG) { + Slog.v(LOG_TAG, "Received A11y Event. event=" + event); } final int eventType = event.getEventType(); @@ -383,9 +374,9 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS, - "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + if (DEBUG) { + Slog.i(LOG_TAG, "Double tap and hold. event=" + + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); } if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); @@ -403,9 +394,9 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS, - "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + if (DEBUG) { + Slog.i(LOG_TAG, "Double tap. event=" + + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); } mAms.onTouchInteractionEnd(); // Remove pending event deliveries. @@ -463,8 +454,8 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureStarted() { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS); + if (DEBUG) { + Slog.i(LOG_TAG, "Gesture started."); } // We have to perform gesture detection, so // clear the current state and try to detect. @@ -479,9 +470,8 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted", - LOGGING_FLAGS, "event=" + gestureEvent); + if (DEBUG) { + Slog.i(LOG_TAG, "Gesture completed. gestureEvent=" + gestureEvent); } endGestureDetection(true); mSendTouchInteractionEndDelayed.cancel(); @@ -491,10 +481,11 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) { - mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS, - "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); + if (DEBUG) { + Slog.i(LOG_TAG, "Gesture cancelled. event=" + + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags); } + if (mState.isGestureDetecting()) { endGestureDetection(event.getActionMasked() == ACTION_UP); return true; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 2e131b696afc..75ec8ea88ace 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -84,7 +84,7 @@ import java.util.concurrent.Executor; * is done and before invoking {@link TransitionCallBack#onResult}. */ public class MagnificationController implements MagnificationConnectionManager.Callback, - MagnificationGestureHandler.Callback, + MagnificationGestureHandler.Callback, MagnificationKeyHandler.Callback, FullScreenMagnificationController.MagnificationInfoChangedCallback, WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { @@ -347,6 +347,36 @@ public class MagnificationController implements MagnificationConnectionManager.C handleUserInteractionChanged(displayId, mode); } + @Override + public void onPanMagnificationStart(int displayId, + @MagnificationController.PanDirection int direction) { + // TODO(b/355499907): Handle multiple pan gestures at the same time (e.g. user may try to + // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same + // speed as non-diagonal movement. + panMagnificationByStep(displayId, direction); + } + + @Override + public void onPanMagnificationStop(int displayId, + @MagnificationController.PanDirection int direction) { + // TODO(b/388847283): Handle held key gestures, which can be used + // for continuous scaling and panning, until they are released. + + } + + @Override + public void onScaleMagnificationStart(int displayId, + @MagnificationController.ZoomDirection int direction) { + scaleMagnificationByStep(displayId, direction); + } + + @Override + public void onScaleMagnificationStop(int displayId, + @MagnificationController.ZoomDirection int direction) { + // TODO(b/388847283): Handle held key gestures, which can be used + // for continuous scaling and panning, until they are released. + } + private void handleUserInteractionChanged(int displayId, int mode) { if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { return; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java new file mode 100644 index 000000000000..a65580c82124 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import android.view.Display; +import android.view.KeyEvent; + +import com.android.server.accessibility.BaseEventStreamTransformation; + +/* + * A class that listens to key presses used to control magnification. + */ +public class MagnificationKeyHandler extends BaseEventStreamTransformation { + + /** Callback interface to report that a user is intending to interact with Magnification. */ + public interface Callback { + /** + * Called when a keyboard shortcut to pan magnification in direction {@code direction} is + * pressed by a user. Note that this can be called for multiple directions if multiple + * arrows are pressed at the same time (e.g. diagonal panning). + * + * @param displayId The logical display ID + * @param direction The direction to start panning + */ + void onPanMagnificationStart(int displayId, + @MagnificationController.PanDirection int direction); + + /** + * Called when a keyboard shortcut to pan magnification in direction {@code direction} is + * unpressed by a user. Note that this can be called for multiple directions if multiple + * arrows had been pressed at the same time (e.g. diagonal panning). + * + * @param displayId The logical display ID + * @param direction The direction in which panning stopped + */ + void onPanMagnificationStop(int displayId, + @MagnificationController.PanDirection int direction); + + /** + * Called when a keyboard shortcut to scale magnification in direction `direction` is + * pressed by a user. + * + * @param displayId The logical display ID + * @param direction The direction in which scaling started + */ + void onScaleMagnificationStart(int displayId, + @MagnificationController.ZoomDirection int direction); + + /** + * Called when a keyboard shortcut to scale magnification in direction `direction` is + * unpressed by a user. + * + * @param displayId The logical display ID + * @param direction The direction in which scaling stopped + */ + void onScaleMagnificationStop(int displayId, + @MagnificationController.ZoomDirection int direction); + } + + protected final MagnificationKeyHandler.Callback mCallback; + + public MagnificationKeyHandler(Callback callback) { + mCallback = callback; + } + + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (!com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()) { + // Send to the rest of the handlers. + super.onKeyEvent(event, policyFlags); + return; + } + boolean modifiersPressed = event.isAltPressed() && event.isMetaPressed(); + if (!modifiersPressed) { + super.onKeyEvent(event, policyFlags); + return; + } + boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; + int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + int panDirection = switch(keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT -> MagnificationController.PAN_DIRECTION_LEFT; + case KeyEvent.KEYCODE_DPAD_RIGHT -> MagnificationController.PAN_DIRECTION_RIGHT; + case KeyEvent.KEYCODE_DPAD_UP -> MagnificationController.PAN_DIRECTION_UP; + default -> MagnificationController.PAN_DIRECTION_DOWN; + }; + if (isDown) { + mCallback.onPanMagnificationStart(getDisplayId(event), panDirection); + } else { + mCallback.onPanMagnificationStop(getDisplayId(event), panDirection); + } + return; + } else if (keyCode == KeyEvent.KEYCODE_EQUALS || keyCode == KeyEvent.KEYCODE_MINUS) { + int zoomDirection = MagnificationController.ZOOM_DIRECTION_OUT; + if (keyCode == KeyEvent.KEYCODE_EQUALS) { + zoomDirection = MagnificationController.ZOOM_DIRECTION_IN; + } + if (isDown) { + mCallback.onScaleMagnificationStart(getDisplayId(event), zoomDirection); + } else { + mCallback.onScaleMagnificationStop(getDisplayId(event), zoomDirection); + } + return; + } + + // Continue down the eventing chain if this was unused. + super.onKeyEvent(event, policyFlags); + } + + private int getDisplayId(KeyEvent event) { + // Display ID may be invalid, e.g. for external keyboard attached to phone. + // In that case, use the default display. + if (event.getDisplayId() != Display.INVALID_DISPLAY) { + return event.getDisplayId(); + } + return Display.DEFAULT_DISPLAY; + } +} diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 5af2346650ed..2143aaaa4cd6 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -236,12 +236,13 @@ public class UserBackupManagerService { // If an app is busy when we want to do a full-data backup, how long to defer the retry. // This is fuzzed, so there are two parameters; backoff_min + Rand[0, backoff_fuzz) - private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour - private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours + private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour + private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours private static final String SERIAL_ID_FILE = "serial_id"; private final @UserIdInt int mUserId; + private final String mLogIdMsg; // Prepended to Logcat messages. private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final TransportManager mTransportManager; @@ -259,13 +260,13 @@ public class UserBackupManagerService { private final IBackupManager mBackupManagerBinder; - private boolean mEnabled; // writes to this are synchronized on 'this' + private boolean mEnabled; // writes to this are synchronized on 'this' private boolean mSetupComplete; private boolean mAutoRestore; private final PendingIntent mRunInitIntent; - private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names + private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names // map UIDs to the set of participating packages under that UID private final SparseArray<HashSet<String>> mBackupParticipants = new SparseArray<>(); @@ -315,8 +316,7 @@ public class UserBackupManagerService { private final File mBaseStateDir; private final File mDataDir; private final File mJournalDir; - @Nullable - private DataChangedJournal mJournal; + @Nullable private DataChangedJournal mJournal; private final File mFullBackupScheduleFile; // Keep a log of all the apps we've ever backed up. @@ -337,7 +337,7 @@ public class UserBackupManagerService { * includes setting up the directories where we keep our bookkeeping and transport management. * * @see #createAndInitializeService(int, Context, BackupManagerService, HandlerThread, File, - * File, TransportManager) + * File, TransportManager) */ static UserBackupManagerService createAndInitializeService( @UserIdInt int userId, @@ -351,7 +351,7 @@ public class UserBackupManagerService { currentTransport = null; } - Slog.d(TAG, addUserIdToLogMessage(userId, "Starting with transport " + currentTransport)); + Slog.d(TAG, "Starting with transport " + currentTransport + " for user " + userId); TransportManager transportManager = new TransportManager(userId, context, transportWhitelist, currentTransport); @@ -361,7 +361,7 @@ public class UserBackupManagerService { HandlerThread userBackupThread = new HandlerThread("backup-" + userId, Process.THREAD_PRIORITY_BACKGROUND); userBackupThread.start(); - Slog.d(TAG, addUserIdToLogMessage(userId, "Started thread " + userBackupThread.getName())); + Slog.d(TAG, "Started thread " + userBackupThread.getName() + " for user " + userId); return createAndInitializeService( userId, @@ -417,26 +417,32 @@ public class UserBackupManagerService { */ public static boolean getSetupCompleteSettingForUser(Context context, int userId) { return Settings.Secure.getIntForUser( - context.getContentResolver(), - Settings.Secure.USER_SETUP_COMPLETE, - 0, - userId) + context.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, + 0, + userId) != 0; } @VisibleForTesting - UserBackupManagerService(Context context, PackageManager packageManager, - LifecycleOperationStorage operationStorage, TransportManager transportManager, - BackupHandler backupHandler, BackupManagerConstants backupManagerConstants, - IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) { + UserBackupManagerService( + Context context, + PackageManager packageManager, + LifecycleOperationStorage operationStorage, + TransportManager transportManager, + BackupHandler backupHandler, + BackupManagerConstants backupManagerConstants, + IActivityManager activityManager, + ActivityManagerInternal activityManagerInternal) { mContext = context; mUserId = 0; + mLogIdMsg = "[UserID:" + mUserId + "] "; mRegisterTransportsRequestedTime = 0; mPackageManager = packageManager; mOperationStorage = operationStorage; - mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage, - mPackageManager, this, mUserId); + mBackupAgentConnectionManager = + new BackupAgentConnectionManager(mOperationStorage, mPackageManager, this, mUserId); mTransportManager = transportManager; mFullBackupQueue = new ArrayList<>(); mBackupHandler = backupHandler; @@ -470,13 +476,14 @@ public class UserBackupManagerService { File dataDir, TransportManager transportManager) { mUserId = userId; + mLogIdMsg = "[UserID:" + mUserId + "] "; mContext = Objects.requireNonNull(context, "context cannot be null"); mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); mActivityManager = ActivityManager.getService(); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); - mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId, mContext, - BackupDestination.CLOUD); + mScheduledBackupEligibility = + getEligibilityRules(mPackageManager, userId, mContext, BackupDestination.CLOUD); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); @@ -484,13 +491,13 @@ public class UserBackupManagerService { Objects.requireNonNull(parent, "parent cannot be null"); mBackupManagerBinder = BackupManagerService.asInterface(parent.asBinder()); - mAgentTimeoutParameters = new - BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver()); + mAgentTimeoutParameters = + new BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver()); mAgentTimeoutParameters.start(); mOperationStorage = new LifecycleOperationStorage(mUserId); - mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage, - mPackageManager, this, mUserId); + mBackupAgentConnectionManager = + new BackupAgentConnectionManager(mOperationStorage, mPackageManager, this, mUserId); Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null"); mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread); @@ -498,8 +505,10 @@ public class UserBackupManagerService { // Set up our bookkeeping final ContentResolver resolver = context.getContentResolver(); mSetupComplete = getSetupCompleteSettingForUser(context, userId); - mAutoRestore = Settings.Secure.getIntForUser(resolver, - Settings.Secure.BACKUP_AUTO_RESTORE, 1, userId) != 0; + mAutoRestore = + Settings.Secure.getIntForUser( + resolver, Settings.Secure.BACKUP_AUTO_RESTORE, 1, userId) + != 0; mSetupObserver = new SetupObserver(this, mBackupHandler); resolver.registerContentObserver( @@ -514,10 +523,7 @@ public class UserBackupManagerService { if (userId == UserHandle.USER_SYSTEM) { mBaseStateDir.mkdirs(); if (!SELinux.restorecon(mBaseStateDir)) { - Slog.w( - TAG, - addUserIdToLogMessage( - userId, "SELinux restorecon failed on " + mBaseStateDir)); + Slog.w(TAG, mLogIdMsg + "SELinux restorecon failed on " + mBaseStateDir); } } @@ -549,8 +555,8 @@ public class UserBackupManagerService { // Set up the backup-request journaling mJournalDir = new File(mBaseStateDir, "pending"); - mJournalDir.mkdirs(); // creates mBaseStateDir along the way - mJournal = null; // will be created on first use + mJournalDir.mkdirs(); // creates mBaseStateDir along the way + mJournal = null; // will be created on first use mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver()); // We are observing changes to the constants throughout the lifecycle of BMS. This is @@ -580,14 +586,20 @@ public class UserBackupManagerService { // if so delete expired events and do not print them to dumpsys BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils = new BackupManagerMonitorDumpsysUtils(); - mBackupHandler.postDelayed(backupManagerMonitorDumpsysUtils::deleteExpiredBMMEvents, + mBackupHandler.postDelayed( + backupManagerMonitorDumpsysUtils::deleteExpiredBMMEvents, INITIALIZATION_DELAY_MILLIS); mBackupPreferences = new UserBackupPreferences(mContext, mBaseStateDir); // Power management - mWakelock = new BackupWakeLock(mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "*backup*-" + userId + "-" + userBackupThread.getThreadId()), userId, mConstants); + mWakelock = + new BackupWakeLock( + mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "*backup*-" + userId + "-" + userBackupThread.getThreadId()), + userId, + mConstants); // Set up the various sorts of package tracking we do mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule"); @@ -788,14 +800,13 @@ public class UserBackupManagerService { mPendingInits.clear(); } - public void setRunningFullBackupTask( - PerformFullTransportBackupTask runningFullBackupTask) { + public void setRunningFullBackupTask(PerformFullTransportBackupTask runningFullBackupTask) { mRunningFullBackupTask = runningFullBackupTask; } /** - * Utility: build a new random integer token. The low bits are the ordinal of the operation for - * near-time uniqueness, and the upper bits are random for app-side unpredictability. + * Utility: build a new random integer token. The low bits are the ordinal of the operation for + * near-time uniqueness, and the upper bits are random for app-side unpredictability. */ public int generateRandomIntegerToken() { int token = mTokenGenerator.nextInt(); @@ -815,16 +826,14 @@ public class UserBackupManagerService { public BackupAgent makeMetadataAgentWithEligibilityRules( BackupEligibilityRules backupEligibilityRules) { - PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId, - backupEligibilityRules); + PackageManagerBackupAgent pmAgent = + new PackageManagerBackupAgent(mPackageManager, mUserId, backupEligibilityRules); pmAgent.attach(mContext); pmAgent.onCreate(UserHandle.of(mUserId)); return pmAgent; } - /** - * Same as {@link #makeMetadataAgent()} but with explicit package-set configuration. - */ + /** Same as {@link #makeMetadataAgent()} but with explicit package-set configuration. */ public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, packages, mUserId); @@ -834,12 +843,12 @@ public class UserBackupManagerService { } private void initPackageTracking() { - if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "` tracking")); + if (DEBUG) Slog.v(TAG, mLogIdMsg + "` tracking"); // Remember our ancestral dataset mTokenFile = new File(mBaseStateDir, "ancestral"); - try (DataInputStream tokenStream = new DataInputStream(new BufferedInputStream( - new FileInputStream(mTokenFile)))) { + try (DataInputStream tokenStream = + new DataInputStream(new BufferedInputStream(new FileInputStream(mTokenFile)))) { int version = tokenStream.readInt(); if (version == CURRENT_ANCESTRAL_RECORD_VERSION) { mAncestralToken = tokenStream.readLong(); @@ -856,9 +865,9 @@ public class UserBackupManagerService { } } catch (FileNotFoundException fnf) { // Probably innocuous - Slog.d(TAG, addUserIdToLogMessage(mUserId, "No ancestral data")); + Slog.d(TAG, mLogIdMsg + "No ancestral data"); } catch (IOException e) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "Unable to read token file"), e); + Slog.w(TAG, mLogIdMsg + "Unable to read token file", e); } mProcessedPackagesJournal = new ProcessedPackagesJournal(mBaseStateDir); @@ -899,20 +908,20 @@ public class UserBackupManagerService { boolean changed = false; ArrayList<FullBackupEntry> schedule = null; List<PackageInfo> apps = - PackageManagerBackupAgent.getStorableApplications(mPackageManager, mUserId, - mScheduledBackupEligibility); + PackageManagerBackupAgent.getStorableApplications( + mPackageManager, mUserId, mScheduledBackupEligibility); if (mFullBackupScheduleFile.exists()) { try (FileInputStream fstream = new FileInputStream(mFullBackupScheduleFile); - BufferedInputStream bufStream = new BufferedInputStream(fstream); - DataInputStream in = new DataInputStream(bufStream)) { + BufferedInputStream bufStream = new BufferedInputStream(fstream); + DataInputStream in = new DataInputStream(bufStream)) { int version = in.readInt(); if (version != SCHEDULE_FILE_VERSION) { // The file version doesn't match the expected value. // Since this is within a "try" block, this exception will be treated like // any other exception, and caught below. - throw new IllegalArgumentException("Unknown backup schedule version " - + version); + throw new IllegalArgumentException( + "Unknown backup schedule version " + version); } final int numPackages = in.readInt(); @@ -935,12 +944,20 @@ public class UserBackupManagerService { pkg.applicationInfo)) { schedule.add(new FullBackupEntry(pkgName, lastBackup)); } else { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Package " + pkgName - + " no longer eligible for full backup")); + Slog.i( + TAG, + mLogIdMsg + + "Package " + + pkgName + + " no longer eligible for full backup"); } } catch (NameNotFoundException e) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Package " + pkgName - + " not installed; dropping from full backup")); + Slog.i( + TAG, + mLogIdMsg + + "Package " + + pkgName + + " not installed; dropping from full backup"); } } @@ -954,11 +971,10 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "New full backup app " - + app.packageName - + " found")); + mLogIdMsg + + "New full backup app " + + app.packageName + + " found"); } schedule.add(new FullBackupEntry(app.packageName, 0)); changed = true; @@ -968,7 +984,7 @@ public class UserBackupManagerService { Collections.sort(schedule); } catch (Exception e) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "Unable to read backup schedule"), e); + Slog.e(TAG, mLogIdMsg + "Unable to read backup schedule", e); mFullBackupScheduleFile.delete(); schedule = null; } @@ -994,46 +1010,43 @@ public class UserBackupManagerService { return schedule; } - private Runnable mFullBackupScheduleWriter = new Runnable() { - @Override - public void run() { - synchronized (mQueueLock) { - try { - ByteArrayOutputStream bufStream = new ByteArrayOutputStream(4096); - DataOutputStream bufOut = new DataOutputStream(bufStream); - bufOut.writeInt(SCHEDULE_FILE_VERSION); - - // version 1: - // - // [int] # of packages in the queue = N - // N * { - // [utf8] package name - // [long] last backup time for this package - // } - int numPackages = mFullBackupQueue.size(); - bufOut.writeInt(numPackages); - - for (int i = 0; i < numPackages; i++) { - FullBackupEntry entry = mFullBackupQueue.get(i); - bufOut.writeUTF(entry.packageName); - bufOut.writeLong(entry.lastBackup); + private Runnable mFullBackupScheduleWriter = + new Runnable() { + @Override + public void run() { + synchronized (mQueueLock) { + try { + ByteArrayOutputStream bufStream = new ByteArrayOutputStream(4096); + DataOutputStream bufOut = new DataOutputStream(bufStream); + bufOut.writeInt(SCHEDULE_FILE_VERSION); + + // version 1: + // + // [int] # of packages in the queue = N + // N * { + // [utf8] package name + // [long] last backup time for this package + // } + int numPackages = mFullBackupQueue.size(); + bufOut.writeInt(numPackages); + + for (int i = 0; i < numPackages; i++) { + FullBackupEntry entry = mFullBackupQueue.get(i); + bufOut.writeUTF(entry.packageName); + bufOut.writeLong(entry.lastBackup); + } + bufOut.flush(); + + AtomicFile af = new AtomicFile(mFullBackupScheduleFile); + FileOutputStream out = af.startWrite(); + out.write(bufStream.toByteArray()); + af.finishWrite(out); + } catch (Exception e) { + Slog.e(TAG, mLogIdMsg + "Unable to write backup schedule!", e); + } } - bufOut.flush(); - - AtomicFile af = new AtomicFile(mFullBackupScheduleFile); - FileOutputStream out = af.startWrite(); - out.write(bufStream.toByteArray()); - af.finishWrite(out); - } catch (Exception e) { - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to write backup schedule!"), - e); } - } - } - }; + }; private void writeFullBackupScheduleAsync() { mBackupHandler.removeCallbacks(mFullBackupScheduleWriter); @@ -1044,28 +1057,33 @@ public class UserBackupManagerService { ArrayList<DataChangedJournal> journals = DataChangedJournal.listJournals(mJournalDir); journals.removeAll(Collections.singletonList(mJournal)); if (!journals.isEmpty()) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Found " + journals.size() + " stale backup journal(s), scheduling.")); + Slog.i( + TAG, + mLogIdMsg + + "Found " + + journals.size() + + " stale backup journal(s), scheduling."); } Set<String> packageNames = new LinkedHashSet<>(); for (DataChangedJournal journal : journals) { try { - journal.forEach(packageName -> { - if (packageNames.add(packageName)) { - dataChangedImpl(packageName); - } - }); + journal.forEach( + packageName -> { + if (packageNames.add(packageName)) { + dataChangedImpl(packageName); + } + }); } catch (IOException e) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "Can't read " + journal), e); + Slog.e(TAG, mLogIdMsg + "Can't read " + journal, e); } } if (!packageNames.isEmpty()) { - String msg = "Stale backup journals: Scheduled " + packageNames.size() - + " package(s) total"; + String msg = + "Stale backup journals: Scheduled " + packageNames.size() + " package(s) total"; if (DEBUG) { msg += ": " + packageNames; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, msg)); + Slog.i(TAG, mLogIdMsg + msg); } } @@ -1105,12 +1123,11 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "recordInitPending(" - + isPending - + ") on transport " - + transportName)); + mLogIdMsg + + "recordInitPending(" + + isPending + + ") on transport " + + transportName); } File stateDir = new File(mBaseStateDir, transportDirName); @@ -1170,10 +1187,16 @@ public class UserBackupManagerService { private void onTransportRegistered(String transportName, String transportDirName) { long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime; - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Transport " + transportName + " registered " + timeMs - + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS - + "ms)")); + Slog.d( + TAG, + mLogIdMsg + + "Transport " + + transportName + + " registered " + + timeMs + + "ms after first request (delay = " + + INITIALIZATION_DELAY_MILLIS + + "ms)"); File stateDir = new File(mBaseStateDir, transportDirName); stateDir.mkdirs(); @@ -1185,8 +1208,10 @@ public class UserBackupManagerService { // TODO: pick a better starting time than now + 1 minute long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); + mAlarmManager.set( + AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, + mRunInitIntent); } } } @@ -1195,137 +1220,131 @@ public class UserBackupManagerService { * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our * internal bookkeeping. */ - private BroadcastReceiver mPackageTrackingReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if (DEBUG) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Received broadcast " + intent)); - } + private BroadcastReceiver mPackageTrackingReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, mLogIdMsg + "Received broadcast " + intent); + } - String action = intent.getAction(); - boolean replacing = false; - boolean added = false; - boolean changed = false; - Bundle extras = intent.getExtras(); - String[] packageList = null; + String action = intent.getAction(); + boolean replacing = false; + boolean added = false; + boolean changed = false; + Bundle extras = intent.getExtras(); + String[] packageList = null; + + if (Intent.ACTION_PACKAGE_ADDED.equals(action) + || Intent.ACTION_PACKAGE_REMOVED.equals(action) + || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + Uri uri = intent.getData(); + if (uri == null) { + return; + } - if (Intent.ACTION_PACKAGE_ADDED.equals(action) - || Intent.ACTION_PACKAGE_REMOVED.equals(action) - || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - Uri uri = intent.getData(); - if (uri == null) { - return; - } + String packageName = uri.getSchemeSpecificPart(); + if (packageName != null) { + packageList = new String[] {packageName}; + } - String packageName = uri.getSchemeSpecificPart(); - if (packageName != null) { - packageList = new String[] {packageName}; - } + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); + if (changed) { + // Look at new transport states for package changed events. + String[] components = + intent.getStringArrayExtra( + Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); - if (changed) { - // Look at new transport states for package changed events. - String[] components = - intent.getStringArrayExtra( - Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + if (DEBUG) { + Slog.i(TAG, mLogIdMsg + "Package " + packageName + " changed"); + for (int i = 0; i < components.length; i++) { + Slog.i(TAG, mLogIdMsg + " * " + components[i]); + } + } - if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Package " + packageName + " changed")); - for (int i = 0; i < components.length; i++) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, " * " + components[i])); + mBackupHandler.post( + () -> + mTransportManager.onPackageChanged( + packageName, components)); + return; } - } - mBackupHandler.post( - () -> - mTransportManager.onPackageChanged( - packageName, components)); - return; - } + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { + added = true; + packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + added = false; + packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - added = true; - packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - added = false; - packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } + if (packageList == null || packageList.length == 0) { + return; + } - if (packageList == null || packageList.length == 0) { - return; - } + int uid = extras.getInt(Intent.EXTRA_UID); + if (added) { + synchronized (mBackupParticipants) { + if (replacing) { + // Remove the entry under the old uid and fall through to re-add. If + // an app + // just opted into key/value backup, add it as a known participant. + removePackageParticipantsLocked(packageList, uid); + } + addPackageParticipantsLocked(packageList); + } - int uid = extras.getInt(Intent.EXTRA_UID); - if (added) { - synchronized (mBackupParticipants) { - if (replacing) { - // Remove the entry under the old uid and fall through to re-add. If - // an app - // just opted into key/value backup, add it as a known participant. - removePackageParticipantsLocked(packageList, uid); - } - addPackageParticipantsLocked(packageList); - } + long now = System.currentTimeMillis(); + for (String packageName : packageList) { + try { + PackageInfo app = + mPackageManager.getPackageInfoAsUser( + packageName, /* flags */ 0, mUserId); + if (mScheduledBackupEligibility.appGetsFullBackup(app) + && mScheduledBackupEligibility.appIsEligibleForBackup( + app.applicationInfo)) { + enqueueFullBackup(packageName, now); + scheduleNextFullBackupJob(0); + } else { + // The app might have just transitioned out of full-data into + // doing + // key/value backups, or might have just disabled backups + // entirely. Make + // sure it is no longer in the full-data queue. + synchronized (mQueueLock) { + dequeueFullBackupLocked(packageName); + } + writeFullBackupScheduleAsync(); + } - long now = System.currentTimeMillis(); - for (String packageName : packageList) { - try { - PackageInfo app = - mPackageManager.getPackageInfoAsUser( - packageName, /* flags */ 0, mUserId); - if (mScheduledBackupEligibility.appGetsFullBackup(app) - && mScheduledBackupEligibility.appIsEligibleForBackup( - app.applicationInfo)) { - enqueueFullBackup(packageName, now); - scheduleNextFullBackupJob(0); - } else { - // The app might have just transitioned out of full-data into - // doing - // key/value backups, or might have just disabled backups - // entirely. Make - // sure it is no longer in the full-data queue. - synchronized (mQueueLock) { - dequeueFullBackupLocked(packageName); + mBackupHandler.post( + () -> mTransportManager.onPackageAdded(packageName)); + } catch (NameNotFoundException e) { + Slog.w(TAG, mLogIdMsg + "Can't resolve new app " + packageName); } - writeFullBackupScheduleAsync(); } - mBackupHandler.post( - () -> mTransportManager.onPackageAdded(packageName)); - } catch (NameNotFoundException e) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, - "Can't resolve new app " + packageName)); - } - } + // Whenever a package is added or updated we need to update the package + // metadata + // bookkeeping. + dataChangedImpl(PACKAGE_MANAGER_SENTINEL); + } else { + if (!replacing) { + // Outright removal. In the full-data case, the app will be dropped from + // the + // queue when its (now obsolete) name comes up again for backup. + synchronized (mBackupParticipants) { + removePackageParticipantsLocked(packageList, uid); + } + } - // Whenever a package is added or updated we need to update the package - // metadata - // bookkeeping. - dataChangedImpl(PACKAGE_MANAGER_SENTINEL); - } else { - if (!replacing) { - // Outright removal. In the full-data case, the app will be dropped from - // the - // queue when its (now obsolete) name comes up again for backup. - synchronized (mBackupParticipants) { - removePackageParticipantsLocked(packageList, uid); + for (String packageName : packageList) { + mBackupHandler.post( + () -> mTransportManager.onPackageRemoved(packageName)); + } } } - - for (String packageName : packageList) { - mBackupHandler.post( - () -> mTransportManager.onPackageRemoved(packageName)); - } - } - } - }; + }; // Add the backup agents in the given packages to our set of known backup participants. // If 'packageNames' is null, adds all backup agents in the whole system. @@ -1334,29 +1353,23 @@ public class UserBackupManagerService { List<PackageInfo> targetApps = allAgentPackages(); if (packageNames != null) { if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "addPackageParticipantsLocked: #" + packageNames.length)); + Slog.v(TAG, mLogIdMsg + "addPackageParticipantsLocked: #" + packageNames.length); } for (String packageName : packageNames) { addPackageParticipantsLockedInner(packageName, targetApps); } } else { if (DEBUG) { - Slog.v(TAG, addUserIdToLogMessage(mUserId, "addPackageParticipantsLocked: all")); + Slog.v(TAG, mLogIdMsg + "addPackageParticipantsLocked: all"); } addPackageParticipantsLockedInner(null, targetApps); } } - private void addPackageParticipantsLockedInner(String packageName, - List<PackageInfo> targetPkgs) { + private void addPackageParticipantsLockedInner( + String packageName, List<PackageInfo> targetPkgs) { if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "Examining " + packageName + " for backup agent")); + Slog.v(TAG, mLogIdMsg + "Examining " + packageName + " for backup agent"); } for (PackageInfo pkg : targetPkgs) { @@ -1368,17 +1381,14 @@ public class UserBackupManagerService { mBackupParticipants.put(uid, set); } set.add(pkg.packageName); - if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "Agent found; added")); + if (DEBUG) Slog.v(TAG, mLogIdMsg + "Agent found; added"); // Schedule a backup for it on general principles if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Scheduling backup for new app " + pkg.packageName)); + Slog.i(TAG, mLogIdMsg + "Scheduling backup for new app " + pkg.packageName); } - Message msg = mBackupHandler - .obtainMessage(MSG_SCHEDULE_BACKUP_PACKAGE, pkg.packageName); + Message msg = + mBackupHandler.obtainMessage(MSG_SCHEDULE_BACKUP_PACKAGE, pkg.packageName); mBackupHandler.sendMessage(msg); } } @@ -1387,19 +1397,18 @@ public class UserBackupManagerService { // Remove the given packages' entries from our known active set. private void removePackageParticipantsLocked(String[] packageNames, int oldUid) { if (packageNames == null) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "removePackageParticipants with null list")); + Slog.w(TAG, mLogIdMsg + "removePackageParticipants with null list"); return; } if (DEBUG) { Slog.v( TAG, - addUserIdToLogMessage( - mUserId, - "removePackageParticipantsLocked: uid=" - + oldUid - + " #" - + packageNames.length)); + mLogIdMsg + + "removePackageParticipantsLocked: uid=" + + oldUid + + " #" + + packageNames.length); } for (String pkg : packageNames) { // Known previous UID, so we know which package set to check @@ -1408,10 +1417,7 @@ public class UserBackupManagerService { removePackageFromSetLocked(set, pkg); if (set.isEmpty()) { if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, " last one of this uid; purging set")); + Slog.v(TAG, mLogIdMsg + " last one of this uid; purging set"); } mBackupParticipants.remove(oldUid); } @@ -1419,8 +1425,7 @@ public class UserBackupManagerService { } } - private void removePackageFromSetLocked(final HashSet<String> set, - final String packageName) { + private void removePackageFromSetLocked(final HashSet<String> set, final String packageName) { if (set.contains(packageName)) { // Found it. Remove this one package from the bookkeeping, and // if it's the last participating app under this uid we drop the @@ -1429,9 +1434,7 @@ public class UserBackupManagerService { // bookkeeping so that its current-dataset data will be retrieved // if the app is subsequently reinstalled if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage(mUserId, " removing participant " + packageName)); + Slog.v(TAG, mLogIdMsg + " removing participant " + packageName); } set.remove(packageName); mPendingBackups.remove(packageName); @@ -1456,8 +1459,11 @@ public class UserBackupManagerService { // we will need the shared library path, so look that up and store it here. // This is used implicitly when we pass the PackageInfo object off to // the Activity Manager to launch the app for backup/restore purposes. - app = mPackageManager.getApplicationInfoAsUser(pkg.packageName, - PackageManager.GET_SHARED_LIBRARY_FILES, mUserId); + app = + mPackageManager.getApplicationInfoAsUser( + pkg.packageName, + PackageManager.GET_SHARED_LIBRARY_FILES, + mUserId); pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles; pkg.applicationInfo.sharedLibraryInfos = app.sharedLibraryInfos; } @@ -1479,8 +1485,8 @@ public class UserBackupManagerService { final Intent notification = new Intent(); notification.setAction(BACKUP_FINISHED_ACTION); notification.setPackage(receiver); - notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES - | Intent.FLAG_RECEIVER_FOREGROUND); + notification.addFlags( + Intent.FLAG_INCLUDE_STOPPED_PACKAGES | Intent.FLAG_RECEIVER_FOREGROUND); notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName); mContext.sendBroadcastAsUser(notification, UserHandle.of(mUserId)); } @@ -1506,17 +1512,14 @@ public class UserBackupManagerService { af.writeInt(-1); } else { af.writeInt(mAncestralPackages.size()); - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Ancestral packages: " + mAncestralPackages.size())); + Slog.d(TAG, mLogIdMsg + "Ancestral packages: " + mAncestralPackages.size()); for (String pkgName : mAncestralPackages) { af.writeUTF(pkgName); - if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, " " + pkgName)); + if (DEBUG) Slog.v(TAG, mLogIdMsg + " " + pkgName); } } } catch (IOException e) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "Unable to write token file:"), e); + Slog.w(TAG, mLogIdMsg + "Unable to write token file:", e); } } @@ -1539,45 +1542,39 @@ public class UserBackupManagerService { /** * Clear an application's data, blocking until the operation completes or times out. * - * @param checkFlagAllowClearUserDataOnFailedRestore if {@code true} uses - * {@link ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE} to decide if - * clearing data is allowed after a failed restore. - * + * @param checkFlagAllowClearUserDataOnFailedRestore if {@code true} uses {@link + * ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE} to decide if + * clearing data is allowed after a failed restore. * @param keepSystemState if {@code true}, we don't clear system state such as already restored - * notification settings, permission grants, etc. + * notification settings, permission grants, etc. */ - private void clearApplicationDataSynchronous(String packageName, - boolean checkFlagAllowClearUserDataOnFailedRestore, boolean keepSystemState) { + private void clearApplicationDataSynchronous( + String packageName, + boolean checkFlagAllowClearUserDataOnFailedRestore, + boolean keepSystemState) { try { - ApplicationInfo applicationInfo = mPackageManager.getPackageInfoAsUser( - packageName, 0, mUserId).applicationInfo; + ApplicationInfo applicationInfo = + mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId).applicationInfo; boolean shouldClearData; if (checkFlagAllowClearUserDataOnFailedRestore && applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q) { - shouldClearData = (applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE) != 0; + int clearOnFailedRestoreFlag = + ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE; + shouldClearData = (applicationInfo.privateFlags & clearOnFailedRestoreFlag) != 0; } else { shouldClearData = - (applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) != 0; + (applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) != 0; } if (!shouldClearData) { if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, - "Clearing app data is not allowed so not wiping " - + packageName)); + Slog.i(TAG, mLogIdMsg + "Clearing app data is not allowed " + packageName); } return; } } catch (NameNotFoundException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Tried to clear data for " + packageName + " but not found")); + Slog.w(TAG, mLogIdMsg + "Tried to clear data for " + packageName + " but not found"); return; } @@ -1585,8 +1582,8 @@ public class UserBackupManagerService { synchronized (mClearDataLock) { mClearingData = true; - mActivityManagerInternal.clearApplicationUserData(packageName, keepSystemState, - /*isRestore=*/ true, observer, mUserId); + mActivityManagerInternal.clearApplicationUserData( + packageName, keepSystemState, /* isRestore= */ true, observer, mUserId); // Only wait 30 seconds for the clear data to happen. long timeoutMark = System.currentTimeMillis() + CLEAR_DATA_TIMEOUT_INTERVAL; @@ -1598,20 +1595,16 @@ public class UserBackupManagerService { mClearingData = false; Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Interrupted while waiting for " - + packageName - + " data to be cleared"), + mLogIdMsg + + "Interrupted while waiting for " + + packageName + + " data to be cleared", e); } } if (mClearingData) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Clearing app data for " + packageName + " timed out")); + Slog.w(TAG, mLogIdMsg + "Clearing app data for " + packageName + " timed out"); } } } @@ -1632,23 +1625,20 @@ public class UserBackupManagerService { * the active set if possible, else the ancestral one. Returns zero if none available. */ public long getAvailableRestoreToken(String packageName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getAvailableRestoreToken"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getAvailableRestoreToken"); long token = mAncestralToken; synchronized (mQueueLock) { if (mCurrentToken != 0 && mProcessedPackagesJournal.hasBeenProcessed(packageName)) { if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "App in ever-stored, so using current token")); + Slog.i(TAG, mLogIdMsg + "App in ever-stored, so using current token"); } token = mCurrentToken; } } if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "getAvailableRestoreToken() == " + token)); + Slog.i(TAG, mLogIdMsg + "getAvailableRestoreToken() == " + token); } return token; } @@ -1666,35 +1656,40 @@ public class UserBackupManagerService { * Requests a backup for the inputted {@code packages} with a specified {@link * IBackupManagerMonitor} and {@link OperationType}. */ - public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags) { - BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = + public int requestBackup( + String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags) { + BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = getBMMEventSender(monitor); mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup"); if (packages == null || packages.length < 1) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "No packages named for backup request")); + Slog.e(TAG, mLogIdMsg + "No packages named for backup request"); BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); mBackupManagerMonitorEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES, - null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); + BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES, null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); throw new IllegalArgumentException("No packages are provided for backup"); } if (!mEnabled || !mSetupComplete) { Slog.i( TAG, - addUserIdToLogMessage(mUserId, "Backup requested but enabled=" + mLogIdMsg + + "Backup requested but enabled=" + mEnabled + " setupComplete=" - + mSetupComplete)); - BackupObserverUtils.sendBackupFinished(observer, - BackupManager.ERROR_BACKUP_NOT_ALLOWED); - final int logTag = mSetupComplete - ? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED - : BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; - mBackupManagerMonitorEventSender.monitorEvent(logTag, null, - BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); + + mSetupComplete); + BackupObserverUtils.sendBackupFinished( + observer, BackupManager.ERROR_BACKUP_NOT_ALLOWED); + final int logTag = + mSetupComplete + ? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED + : BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; + mBackupManagerMonitorEventSender.monitorEvent( + logTag, + null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + null); return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } @@ -1708,31 +1703,45 @@ public class UserBackupManagerService { transportConnection = mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()"); backupDestination = getBackupDestinationFromTransport(transportConnection); - } catch (TransportNotRegisteredException | TransportNotAvailableException + } catch (TransportNotRegisteredException + | TransportNotAvailableException | RemoteException e) { BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); mBackupManagerMonitorEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, - null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, null, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); return BackupManager.ERROR_TRANSPORT_ABORTED; } OnTaskFinishedListener listener = caller -> mTransportManager.disposeOfTransportClient(transportConnection, caller); - BackupEligibilityRules backupEligibilityRules = getEligibilityRulesForOperation( - backupDestination); + BackupEligibilityRules backupEligibilityRules = + getEligibilityRulesForOperation(backupDestination); Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP); - msg.obj = getRequestBackupParams(packages, observer, monitor, flags, backupEligibilityRules, - transportConnection, transportDirName, listener); + msg.obj = + getRequestBackupParams( + packages, + observer, + monitor, + flags, + backupEligibilityRules, + transportConnection, + transportDirName, + listener); mBackupHandler.sendMessage(msg); return BackupManager.SUCCESS; } @VisibleForTesting - BackupParams getRequestBackupParams(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags, BackupEligibilityRules backupEligibilityRules, - TransportConnection transportConnection, String transportDirName, + BackupParams getRequestBackupParams( + String[] packages, + IBackupObserver observer, + IBackupManagerMonitor monitor, + int flags, + BackupEligibilityRules backupEligibilityRules, + TransportConnection transportConnection, + String transportDirName, OnTaskFinishedListener listener) { ArrayList<String> fullBackupList = new ArrayList<>(); ArrayList<String> kvBackupList = new ArrayList<>(); @@ -1742,11 +1751,12 @@ public class UserBackupManagerService { continue; } try { - PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_SIGNING_CERTIFICATES, mUserId); + PackageInfo packageInfo = + mPackageManager.getPackageInfoAsUser( + packageName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); if (!backupEligibilityRules.appIsEligibleForBackup(packageInfo.applicationInfo)) { - BackupObserverUtils.sendBackupOnPackageResult(observer, packageName, - BackupManager.ERROR_BACKUP_NOT_ALLOWED); + BackupObserverUtils.sendBackupOnPackageResult( + observer, packageName, BackupManager.ERROR_BACKUP_NOT_ALLOWED); continue; } if (backupEligibilityRules.appGetsFullBackup(packageInfo)) { @@ -1755,31 +1765,41 @@ public class UserBackupManagerService { kvBackupList.add(packageInfo.packageName); } } catch (NameNotFoundException e) { - BackupObserverUtils.sendBackupOnPackageResult(observer, packageName, - BackupManager.ERROR_PACKAGE_NOT_FOUND); + BackupObserverUtils.sendBackupOnPackageResult( + observer, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND); } } - EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(), + EventLog.writeEvent( + EventLogTags.BACKUP_REQUESTED, + packages.length, + kvBackupList.size(), fullBackupList.size()); if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "Backup requested for " - + packages.length - + " packages, of them: " - + fullBackupList.size() - + " full backups, " - + kvBackupList.size() - + " k/v backups")); + mLogIdMsg + + "Backup requested for " + + packages.length + + " packages, of them: " + + fullBackupList.size() + + " full backups, " + + kvBackupList.size() + + " k/v backups"); } boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0; - return new BackupParams(transportConnection, transportDirName, kvBackupList, fullBackupList, - observer, monitor, listener, /* userInitiated */ true, nonIncrementalBackup, + return new BackupParams( + transportConnection, + transportDirName, + kvBackupList, + fullBackupList, + observer, + monitor, + listener, /* userInitiated */ + true, + nonIncrementalBackup, backupEligibilityRules); } @@ -1787,7 +1807,7 @@ public class UserBackupManagerService { public void cancelBackups() { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups"); if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "cancelBackups() called.")); + Slog.i(TAG, mLogIdMsg + "cancelBackups() called."); } final long oldToken = Binder.clearCallingIdentity(); try { @@ -1795,14 +1815,24 @@ public class UserBackupManagerService { mOperationStorage.operationTokensForOpType(OpType.BACKUP); for (Integer token : operationsToCancel) { - mOperationStorage.cancelOperation(token, /* cancelAll */ true, - operationType -> { /* no callback needed here */ }); + mOperationStorage.cancelOperation( + token, /* cancelAll */ + true, + operationType -> { + /* no callback needed here */ + }); } // We don't want the backup jobs to kick in any time soon. // Reschedules them to run in the distant future. - KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, + KeyValueBackupJob.schedule( + mUserId, + mContext, + BUSY_BACKOFF_MIN_MILLIS, /* userBackupManagerService */ this); - FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, + FullBackupJob.schedule( + mUserId, + mContext, + 2 * BUSY_BACKOFF_MIN_MILLIS, /* userBackupManagerService */ this); } finally { Binder.restoreCallingIdentity(oldToken); @@ -1810,35 +1840,34 @@ public class UserBackupManagerService { } /** Schedule a timeout message for the operation identified by {@code token}. */ - public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback, - int operationType) { + public void prepareOperationTimeout( + int token, long interval, BackupRestoreTask callback, int operationType) { if (operationType != OpType.BACKUP_WAIT && operationType != OpType.RESTORE_WAIT) { Slog.wtf( TAG, - addUserIdToLogMessage( - mUserId, - "prepareOperationTimeout() doesn't support operation " - + Integer.toHexString(token) - + " of type " - + operationType)); + mLogIdMsg + + "prepareOperationTimeout() doesn't support operation " + + Integer.toHexString(token) + + " of type " + + operationType); return; } if (DEBUG) { Slog.v( TAG, - addUserIdToLogMessage( - mUserId, - "starting timeout: token=" - + Integer.toHexString(token) - + " interval=" - + interval - + " callback=" - + callback)); + mLogIdMsg + + "starting timeout: token=" + + Integer.toHexString(token) + + " interval=" + + interval + + " callback=" + + callback); } mOperationStorage.registerOperation(token, OpState.PENDING, callback, operationType); - Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType), - token, 0, callback); + Message msg = + mBackupHandler.obtainMessage( + getMessageIdForOperationType(operationType), token, 0, callback); mBackupHandler.sendMessageDelayed(msg, interval); } @@ -1851,19 +1880,20 @@ public class UserBackupManagerService { default: Slog.wtf( TAG, - addUserIdToLogMessage( - mUserId, - "getMessageIdForOperationType called on invalid operation type: " - + operationType)); + mLogIdMsg + + "getMessageIdForOperationType called on invalid operation type: " + + operationType); return -1; } } /** Block until we received an operation complete message (from the agent or cancellation). */ public boolean waitUntilOperationComplete(int token) { - return mOperationStorage.waitUntilOperationComplete(token, operationType -> { - mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); - }); + return mOperationStorage.waitUntilOperationComplete( + token, + operationType -> { + mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); + }); } /** Cancel the operation associated with {@code token}. */ @@ -1871,11 +1901,15 @@ public class UserBackupManagerService { // Remove all pending timeout messages of types OpType.BACKUP_WAIT and // OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and // doesn't require cancellation. - mOperationStorage.cancelOperation(token, cancelAll, operationType -> { - if (operationType == OpType.BACKUP_WAIT || operationType == OpType.RESTORE_WAIT) { - mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); - } - }); + mOperationStorage.cancelOperation( + token, + cancelAll, + operationType -> { + if (operationType == OpType.BACKUP_WAIT + || operationType == OpType.RESTORE_WAIT) { + mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); + } + }); } /** Returns {@code true} if a backup is currently running, else returns {@code false}. */ @@ -1885,9 +1919,7 @@ public class UserBackupManagerService { // ----- Full-data backup scheduling ----- - /** - * Schedule a job to tell us when it's a good time to run a full backup - */ + /** Schedule a job to tell us when it's a good time to run a full backup */ public void scheduleNextFullBackupJob(long transportMinLatency) { synchronized (mQueueLock) { if (mFullBackupQueue.size() > 0) { @@ -1899,18 +1931,15 @@ public class UserBackupManagerService { final long interval = mConstants.getFullBackupIntervalMilliseconds(); final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0; final long latency = Math.max(transportMinLatency, appLatency); - FullBackupJob.schedule(mUserId, mContext, latency, - /* userBackupManagerService */ this); + FullBackupJob.schedule( + mUserId, mContext, latency, /* userBackupManagerService */ this); } else { - Slog.i(TAG, - addUserIdToLogMessage(mUserId, "Full backup queue empty; not scheduling")); + Slog.i(TAG, mLogIdMsg + "Full backup queue empty; not scheduling"); } } } - /** - * Remove a package from the full-data queue. - */ + /** Remove a package from the full-data queue. */ @GuardedBy("mQueueLock") private void dequeueFullBackupLocked(String packageName) { final int numPackages = mFullBackupQueue.size(); @@ -1922,9 +1951,7 @@ public class UserBackupManagerService { } } - /** - * Enqueue full backup for the given app, with a note about when it last ran. - */ + /** Enqueue full backup for the given app, with a note about when it last ran. */ public void enqueueFullBackup(String packageName, long lastBackedUp) { FullBackupEntry newEntry = new FullBackupEntry(packageName, lastBackedUp); synchronized (mQueueLock) { @@ -1957,10 +1984,7 @@ public class UserBackupManagerService { private boolean fullBackupAllowable(String transportName) { if (!mTransportManager.isTransportRegistered(transportName)) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Transport not registered; full data backup not performed")); + Slog.w(TAG, mLogIdMsg + "Transport not registered; full data backup not performed"); return false; } @@ -1971,15 +1995,11 @@ public class UserBackupManagerService { File stateDir = new File(mBaseStateDir, transportDirName); File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL); if (pmState.length() <= 0) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Full backup requested but dataset not yet initialized")); + Slog.i(TAG, mLogIdMsg + "Full backup requested but dataset not yet initialized"); return false; } } catch (Exception e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to get transport name: " + e.getMessage())); + Slog.w(TAG, mLogIdMsg + "Unable to get transport name: " + e.getMessage()); return false; } @@ -1987,14 +2007,14 @@ public class UserBackupManagerService { } /** - * Conditions are right for a full backup operation, so run one. The model we use is - * to perform one app backup per scheduled job execution, and to reschedule the job - * with zero latency as long as conditions remain right and we still have work to do. + * Conditions are right for a full backup operation, so run one. The model we use is to perform + * one app backup per scheduled job execution, and to reschedule the job with zero latency as + * long as conditions remain right and we still have work to do. * * <p>This is the "start a full backup operation" entry point called by the scheduled job. * - * @return Whether ongoing work will continue. The return value here will be passed - * along as the return value to the scheduled job's onStartJob() callback. + * @return Whether ongoing work will continue. The return value here will be passed along as the + * return value to the scheduled job's onStartJob() callback. */ public boolean beginFullBackup(FullBackupJob scheduledJob) { final long now = System.currentTimeMillis(); @@ -2012,33 +2032,34 @@ public class UserBackupManagerService { // the job driving automatic backups; that job will be scheduled again when // the user enables backup. if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "beginFullBackup but enabled=" + mEnabled - + " setupComplete=" + mSetupComplete + "; ignoring")); + Slog.i( + TAG, + mLogIdMsg + + "beginFullBackup but enabled=" + + mEnabled + + " setupComplete=" + + mSetupComplete + + "; ignoring"); } return false; } // Don't run the backup if we're in battery saver mode, but reschedule // to try again in the not-so-distant future. - final PowerSaveState result = - mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP); + final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP); if (result.batterySaverEnabled) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Deferring scheduled full backups in battery saver mode")); - FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, - /* userBackupManagerService */ this); + Slog.i(TAG, mLogIdMsg + "Deferring scheduled full backups in battery saver mode"); + FullBackupJob.schedule( + mUserId, mContext, keyValueBackupInterval, /* userBackupManagerService */ this); return false; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning scheduled full backup operation")); + Slog.i(TAG, mLogIdMsg + "Beginning scheduled full backup operation"); // Great; we're able to run full backup jobs now. See if we have any work to do. synchronized (mQueueLock) { if (mRunningFullBackupTask != null) { - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Backup triggered but one already/still running!")); + Slog.e(TAG, mLogIdMsg + "Backup triggered but one already/still running!"); return false; } @@ -2053,8 +2074,7 @@ public class UserBackupManagerService { // have emptied the queue. if (mFullBackupQueue.size() == 0) { // no work to do so just bow out - Slog.i(TAG, - addUserIdToLogMessage(mUserId, "Backup queue empty; doing nothing")); + Slog.i(TAG, mLogIdMsg + "Backup queue empty; doing nothing"); runBackup = false; break; } @@ -2064,10 +2084,7 @@ public class UserBackupManagerService { String transportName = mTransportManager.getCurrentTransportName(); if (!fullBackupAllowable(transportName)) { if (DEBUG) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Preconditions not met; not running full backup")); + Slog.i(TAG, mLogIdMsg + "Preconditions not met; not running full backup"); } runBackup = false; // Typically this means we haven't run a key/value backup yet. Back off @@ -2085,18 +2102,16 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "Device ready but too early to back up next app")); + mLogIdMsg + "Device ready but too early to back up next app"); } // Wait until the next app in the queue falls due for a full data backup latency = fullBackupInterval - timeSinceRun; - break; // we know we aren't doing work yet, so bail. + break; // we know we aren't doing work yet, so bail. } try { - PackageInfo appInfo = mPackageManager.getPackageInfoAsUser( - entry.packageName, 0, mUserId); + PackageInfo appInfo = + mPackageManager.getPackageInfoAsUser(entry.packageName, 0, mUserId); if (!mScheduledBackupEligibility.appGetsFullBackup(appInfo)) { // The head app isn't supposed to get full-data backups [any more]; // so we cull it and force a loop around to consider the new head @@ -2104,12 +2119,11 @@ public class UserBackupManagerService { if (DEBUG) { Slog.i( TAG, - addUserIdToLogMessage( - mUserId, - "Culling package " - + entry.packageName - + " in full-backup queue but not" - + " eligible")); + mLogIdMsg + + "Culling package " + + entry.packageName + + " in full-backup queue but not" + + " eligible"); } mFullBackupQueue.remove(0); headBusy = true; // force the while() condition @@ -2117,19 +2131,24 @@ public class UserBackupManagerService { } final int privFlags = appInfo.applicationInfo.privateFlags; - headBusy = (privFlags & PRIVATE_FLAG_BACKUP_IN_FOREGROUND) == 0 - && mActivityManagerInternal.isAppForeground( - appInfo.applicationInfo.uid); + headBusy = + (privFlags & PRIVATE_FLAG_BACKUP_IN_FOREGROUND) == 0 + && mActivityManagerInternal.isAppForeground( + appInfo.applicationInfo.uid); if (headBusy) { - final long nextEligible = System.currentTimeMillis() - + BUSY_BACKOFF_MIN_MILLIS - + mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ); + final long nextEligible = + System.currentTimeMillis() + + BUSY_BACKOFF_MIN_MILLIS + + mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Full backup time but " + entry.packageName - + " is busy; deferring to " + sdf.format( - new Date(nextEligible)))); + Slog.i( + TAG, + mLogIdMsg + + "Full backup time but " + + entry.packageName + + " is busy; deferring to " + + sdf.format(new Date(nextEligible))); // This relocates the app's entry from the head of the queue to // its order-appropriate position further down, so upon looping // a new candidate will be considered at the head. @@ -2147,21 +2166,22 @@ public class UserBackupManagerService { if (runBackup) { CountDownLatch latch = new CountDownLatch(1); - String[] pkg = new String[]{entry.packageName}; + String[] pkg = new String[] {entry.packageName}; try { - mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport( - this, - mOperationStorage, - /* observer */ null, - pkg, - /* updateSchedule */ true, - scheduledJob, - latch, - /* backupObserver */ null, - /* monitor */ null, - /* userInitiated */ false, - "BMS.beginFullBackup()", - getEligibilityRulesForOperation(BackupDestination.CLOUD)); + mRunningFullBackupTask = + PerformFullTransportBackupTask.newWithCurrentTransport( + this, + mOperationStorage, + /* observer */ null, + pkg, + /* updateSchedule */ true, + scheduledJob, + latch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + "BMS.beginFullBackup()", + getEligibilityRulesForOperation(BackupDestination.CLOUD)); } catch (IllegalStateException e) { Slog.w(TAG, "Failed to start backup", e); runBackup = false; @@ -2169,12 +2189,15 @@ public class UserBackupManagerService { } if (!runBackup) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, - "Nothing pending full backup or failed to start the " - + "operation; rescheduling +" + latency)); - final long deferTime = latency; // pin for the closure - FullBackupJob.schedule(mUserId, mContext, deferTime, - /* userBackupManagerService */ this); + Slog.i( + TAG, + mLogIdMsg + + "Nothing pending full backup or failed to start the " + + "operation; rescheduling +" + + latency); + final long deferTime = latency; // pin for the closure + FullBackupJob.schedule( + mUserId, mContext, deferTime, /* userBackupManagerService */ this); return false; } @@ -2195,21 +2218,22 @@ public class UserBackupManagerService { public void endFullBackup() { // offload the mRunningFullBackupTask.handleCancel() call to another thread, // as we might have to wait for mCancelLock - Runnable endFullBackupRunnable = new Runnable() { - @Override - public void run() { - PerformFullTransportBackupTask pftbt = null; - synchronized (mQueueLock) { - if (mRunningFullBackupTask != null) { - pftbt = mRunningFullBackupTask; + Runnable endFullBackupRunnable = + new Runnable() { + @Override + public void run() { + PerformFullTransportBackupTask pftbt = null; + synchronized (mQueueLock) { + if (mRunningFullBackupTask != null) { + pftbt = mRunningFullBackupTask; + } + } + if (pftbt != null) { + Slog.i(TAG, mLogIdMsg + "Telling running backup to stop"); + pftbt.handleCancel(true); + } } - } - if (pftbt != null) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Telling running backup to stop")); - pftbt.handleCancel(true); - } - } - }; + }; new Thread(endFullBackupRunnable, "end-full-backup").start(); } @@ -2217,7 +2241,7 @@ public class UserBackupManagerService { public void restoreWidgetData(String packageName, byte[] widgetData) { // Apply the restored widget state and generate the ID update for the app if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Incorporating restored widget data")); + Slog.i(TAG, mLogIdMsg + "Incorporating restored widget data"); } AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId); } @@ -2235,13 +2259,12 @@ public class UserBackupManagerService { if (targets == null) { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "dataChanged but no participant pkg='" - + packageName - + "'" - + " uid=" - + Binder.getCallingUid())); + mLogIdMsg + + "dataChanged but no participant pkg='" + + packageName + + "'" + + " uid=" + + Binder.getCallingUid()); return; } @@ -2253,10 +2276,7 @@ public class UserBackupManagerService { BackupRequest req = new BackupRequest(packageName); if (mPendingBackups.put(packageName, req) == null) { if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Now staging backup of " + packageName)); + Slog.d(TAG, mLogIdMsg + "Now staging backup of " + packageName); } // Journal this request in case of crash. The put() @@ -2268,16 +2288,18 @@ public class UserBackupManagerService { } // ...and schedule a backup pass if necessary - KeyValueBackupJob.schedule(mUserId, mContext, - /* userBackupManagerService */ this); + KeyValueBackupJob.schedule(mUserId, mContext, /* userBackupManagerService */ this); } // Note: packageName is currently unused, but may be in the future private HashSet<String> dataChangedTargets(String packageName) { // If the caller does not hold the BACKUP permission, it can only request a // backup of its own data. - if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), - Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { + if ((mContext.checkPermission( + android.Manifest.permission.BACKUP, + Binder.getCallingPid(), + Binder.getCallingUid())) + == PackageManager.PERMISSION_DENIED) { synchronized (mBackupParticipants) { return mBackupParticipants.get(Binder.getCallingUid()); } @@ -2298,10 +2320,7 @@ public class UserBackupManagerService { if (mJournal == null) mJournal = DataChangedJournal.newJournal(mJournalDir); mJournal.addPackage(str); } catch (IOException e) { - Slog.e( - TAG, - addUserIdToLogMessage(mUserId, "Can't write " + str + " to backup journal"), - e); + Slog.e(TAG, mLogIdMsg + "Can't write " + str + " to backup journal", e); mJournal = null; } } @@ -2314,31 +2333,28 @@ public class UserBackupManagerService { if (targets == null) { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "dataChanged but no participant pkg='" - + packageName - + "'" - + " uid=" - + Binder.getCallingUid())); + mLogIdMsg + + "dataChanged but no participant pkg='" + + packageName + + "'" + + " uid=" + + Binder.getCallingUid()); return; } - mBackupHandler.post(new Runnable() { - public void run() { - dataChangedImpl(packageName, targets); - } - }); + mBackupHandler.post( + new Runnable() { + public void run() { + dataChangedImpl(packageName, targets); + } + }); } /** Run an initialize operation for the given transport. */ public void initializeTransports(String[] transportNames, IBackupObserver observer) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "initializeTransport"); - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "initializeTransport(): " + Arrays.asList(transportNames))); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "initializeTransport"); + Slog.d(TAG, mLogIdMsg + "initializeTransport(): " + Arrays.asList(transportNames)); final long oldId = Binder.clearCallingIdentity(); try { @@ -2351,32 +2367,23 @@ public class UserBackupManagerService { } } - /** - * Sets the work profile serial number of the ancestral work profile. - */ + /** Sets the work profile serial number of the ancestral work profile. */ public void setAncestralSerialNumber(long ancestralSerialNumber) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, - "setAncestralSerialNumber"); - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Setting ancestral work profile id to " + ancestralSerialNumber)); + mContext.enforceCallingPermission( + android.Manifest.permission.BACKUP, "setAncestralSerialNumber"); + Slog.d(TAG, mLogIdMsg + "Setting ancestral work profile id to " + ancestralSerialNumber); try (RandomAccessFile af = new RandomAccessFile(getAncestralSerialNumberFile(), /* mode */ "rwd")) { af.writeLong(ancestralSerialNumber); } catch (IOException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to write to work profile serial mapping file:"), - e); + Slog.w(TAG, mLogIdMsg + "Unable to write to work profile serial mapping file:", e); } } /** - * Returns the work profile serial number of the ancestral device. This will be set by - * {@link #setAncestralSerialNumber(long)}. Will return {@code -1} if not set. + * Returns the work profile serial number of the ancestral device. This will be set by {@link + * #setAncestralSerialNumber(long)}. Will return {@code -1} if not set. */ public long getAncestralSerialNumber() { try (RandomAccessFile af = @@ -2385,20 +2392,15 @@ public class UserBackupManagerService { } catch (FileNotFoundException e) { // It's OK not to have the file present, so we just return -1 to indicate no value. } catch (IOException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to read work profile serial number file:"), - e); + Slog.w(TAG, mLogIdMsg + "Unable to read work profile serial number file:", e); } return -1; } private File getAncestralSerialNumberFile() { if (mAncestralSerialNumberFile == null) { - mAncestralSerialNumberFile = new File( - UserBackupManagerFiles.getBaseStateDir(getUserId()), - SERIAL_ID_FILE); + mAncestralSerialNumberFile = + new File(UserBackupManagerFiles.getBaseStateDir(getUserId()), SERIAL_ID_FILE); } return mAncestralSerialNumberFile; } @@ -2408,39 +2410,36 @@ public class UserBackupManagerService { mAncestralSerialNumberFile = ancestralSerialNumberFile; } - /** Clear the given package's backup data from the current transport. */ public void clearBackupData(String transportName, String packageName) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "clearBackupData() of " + packageName + " on " + transportName)); + Slog.d(TAG, mLogIdMsg + "clearBackupData() of " + packageName + " on " + transportName); PackageInfo info; try { - info = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_SIGNING_CERTIFICATES, mUserId); + info = + mPackageManager.getPackageInfoAsUser( + packageName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); } catch (NameNotFoundException e) { Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "No such package '" + packageName + "' - not clearing backup data")); + mLogIdMsg + "No such package '" + packageName + "' - not clearing backup data"); return; } // If the caller does not hold the BACKUP permission, it can only request a // wipe of its own backed-up data. Set<String> apps; - if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), - Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { + if ((mContext.checkPermission( + android.Manifest.permission.BACKUP, + Binder.getCallingPid(), + Binder.getCallingUid())) + == PackageManager.PERMISSION_DENIED) { apps = mBackupParticipants.get(Binder.getCallingUid()); } else { // a caller with full permission can ask to back up any participating app // !!! TODO: allow data-clear of ANY app? if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "Privileged caller, allowing clear of other apps")); + Slog.v(TAG, mLogIdMsg + "Privileged caller, allowing clear of other apps"); } apps = mProcessedPackagesJournal.getPackagesCopy(); } @@ -2448,30 +2447,33 @@ public class UserBackupManagerService { if (apps.contains(packageName)) { // found it; fire off the clear request if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage(mUserId, "Found the app - running clear process")); + Slog.v(TAG, mLogIdMsg + "Found the app - running clear process"); } mBackupHandler.removeMessages(MSG_RETRY_CLEAR); synchronized (mQueueLock) { TransportConnection transportConnection = - mTransportManager - .getTransportClient(transportName, "BMS.clearBackupData()"); + mTransportManager.getTransportClient( + transportName, "BMS.clearBackupData()"); if (transportConnection == null) { // transport is currently unregistered -- make sure to retry - Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR, - new ClearRetryParams(transportName, packageName)); + Message msg = + mBackupHandler.obtainMessage( + MSG_RETRY_CLEAR, + new ClearRetryParams(transportName, packageName)); mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL); return; } final long oldId = Binder.clearCallingIdentity(); try { - OnTaskFinishedListener listener = caller -> mTransportManager - .disposeOfTransportClient(transportConnection, caller); + OnTaskFinishedListener listener = + caller -> + mTransportManager.disposeOfTransportClient( + transportConnection, caller); mWakelock.acquire(); - Message msg = mBackupHandler.obtainMessage( - MSG_RUN_CLEAR, - new ClearParams(transportConnection, info, listener)); + Message msg = + mBackupHandler.obtainMessage( + MSG_RUN_CLEAR, + new ClearParams(transportConnection, info, listener)); mBackupHandler.sendMessage(msg); } finally { Binder.restoreCallingIdentity(oldId); @@ -2492,31 +2494,24 @@ public class UserBackupManagerService { final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP); if (result.batterySaverEnabled) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Not running backup while in battery save mode")); + Slog.d(TAG, mLogIdMsg + "Not running backup while in battery save mode"); // Try again in several hours. - KeyValueBackupJob.schedule(mUserId, mContext, - /* userBackupManagerService */ this); + KeyValueBackupJob.schedule(mUserId, mContext, /* userBackupManagerService */ this); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass")); + Slog.d(TAG, mLogIdMsg + "Scheduling immediate backup pass"); synchronized (getQueueLock()) { if (getPendingInits().size() > 0) { // If there are pending init operations, we process those and then settle // into the usual periodic backup schedule. if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "Init pending at scheduled backup")); + Slog.v(TAG, mLogIdMsg + "Init pending at scheduled backup"); } try { getAlarmManager().cancel(mRunInitIntent); mRunInitIntent.send(); } catch (PendingIntent.CanceledException ce) { - Slog.w( - TAG, - addUserIdToLogMessage(mUserId, "Run init intent cancelled")); + Slog.w(TAG, mLogIdMsg + "Run init intent cancelled"); } return; } @@ -2526,8 +2521,11 @@ public class UserBackupManagerService { if (!isEnabled() || !isSetupComplete()) { Slog.w( TAG, - addUserIdToLogMessage(mUserId, "Backup pass but enabled=" + isEnabled() - + " setupComplete=" + isSetupComplete())); + mLogIdMsg + + "Backup pass but enabled=" + + isEnabled() + + " setupComplete=" + + isSetupComplete()); return; } @@ -2548,9 +2546,17 @@ public class UserBackupManagerService { * return to the caller until the backup has been completed. It requires on-screen confirmation * by the user. */ - public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, - boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps, - boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) { + public void adbBackup( + ParcelFileDescriptor fd, + boolean includeApks, + boolean includeObbs, + boolean includeShared, + boolean doWidgets, + boolean doAllApps, + boolean includeSystem, + boolean compress, + boolean doKeyValue, + String[] pkgList) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup"); final int callingUserHandle = UserHandle.getCallingUserId(); @@ -2574,47 +2580,66 @@ public class UserBackupManagerService { final long oldId = Binder.clearCallingIdentity(); try { if (!mSetupComplete) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup not supported before setup")); + Slog.i(TAG, mLogIdMsg + "Backup not supported before setup"); return; } - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Requesting backup: apks=" + includeApks + " obb=" + includeObbs + " shared=" - + includeShared + " all=" + doAllApps + " system=" + includeSystem - + " includekeyvalue=" + doKeyValue + " pkgs=" + Arrays.toString( - pkgList))); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup...")); - - BackupEligibilityRules eligibilityRules = getEligibilityRulesForOperation( - BackupDestination.ADB_BACKUP); - AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs, - includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue, - pkgList, eligibilityRules); + Slog.d( + TAG, + mLogIdMsg + + "Requesting backup: apks=" + + includeApks + + " obb=" + + includeObbs + + " shared=" + + includeShared + + " all=" + + doAllApps + + " system=" + + includeSystem + + " includekeyvalue=" + + doKeyValue + + " pkgs=" + + Arrays.toString(pkgList)); + Slog.i(TAG, mLogIdMsg + "Beginning adb backup..."); + + BackupEligibilityRules eligibilityRules = + getEligibilityRulesForOperation(BackupDestination.ADB_BACKUP); + AdbBackupParams params = + new AdbBackupParams( + fd, + includeApks, + includeObbs, + includeShared, + doWidgets, + doAllApps, + includeSystem, + compress, + doKeyValue, + pkgList, + eligibilityRules); final int token = generateRandomIntegerToken(); synchronized (mAdbBackupRestoreConfirmations) { mAdbBackupRestoreConfirmations.put(token, params); } // start up the confirmation UI - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Starting backup confirmation UI")); + Slog.d(TAG, mLogIdMsg + "Starting backup confirmation UI"); if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) { - Slog.e( - TAG, - addUserIdToLogMessage(mUserId, "Unable to launch backup confirmation UI")); + Slog.e(TAG, mLogIdMsg + "Unable to launch backup confirmation UI"); mAdbBackupRestoreConfirmations.delete(token); return; } // make sure the screen is lit for the user interaction - mPowerManager.userActivity(SystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_OTHER, - 0); + mPowerManager.userActivity( + SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0); // start the confirmation countdown startConfirmationTimeout(token, params); // wait for the backup to be performed - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Waiting for backup completion...")); + Slog.d(TAG, mLogIdMsg + "Waiting for backup completion..."); waitForCompletion(params); } finally { try { @@ -2622,19 +2647,17 @@ public class UserBackupManagerService { } catch (IOException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "IO error closing output for adb backup: " + e.getMessage())); + mLogIdMsg + "IO error closing output for adb backup: " + e.getMessage()); } Binder.restoreCallingIdentity(oldId); - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Adb backup processing complete.")); + Slog.d(TAG, mLogIdMsg + "Adb backup processing complete."); } } /** Run a full backup pass for the given packages. Used by 'adb shell bmgr'. */ public void fullTransportBackup(String[] pkgNames) { - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, - "fullTransportBackup"); + mContext.enforceCallingPermission( + android.Manifest.permission.BACKUP, "fullTransportBackup"); final int callingUserHandle = UserHandle.getCallingUserId(); // TODO: http://b/22388012 if (callingUserHandle != UserHandle.USER_SYSTEM) { @@ -2643,30 +2666,27 @@ public class UserBackupManagerService { String transportName = mTransportManager.getCurrentTransportName(); if (!fullBackupAllowable(transportName)) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, - "Full backup not currently possible -- key/value backup not yet run?")); + Slog.i(TAG, mLogIdMsg + "Full backup not possible. Key/value backup not yet run?"); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "fullTransportBackup()")); + Slog.d(TAG, mLogIdMsg + "fullTransportBackup()"); final long oldId = Binder.clearCallingIdentity(); try { CountDownLatch latch = new CountDownLatch(1); - Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport( - this, - mOperationStorage, - /* observer */ null, - pkgNames, - /* updateSchedule */ false, - /* runningJob */ null, - latch, - /* backupObserver */ null, - /* monitor */ null, - /* userInitiated */ false, - "BMS.fullTransportBackup()", - getEligibilityRulesForOperation(BackupDestination.CLOUD)); + Runnable task = + PerformFullTransportBackupTask.newWithCurrentTransport( + this, + mOperationStorage, + /* observer */ null, + pkgNames, + /* updateSchedule */ false, + /* runningJob */ null, + latch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + "BMS.fullTransportBackup()", + getEligibilityRulesForOperation(BackupDestination.CLOUD)); // Acquiring wakelock for PerformFullTransportBackupTask before its start. mWakelock.acquire(); (new Thread(task, "full-transport-master")).start(); @@ -2692,7 +2712,7 @@ public class UserBackupManagerService { } } - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Done with full transport backup.")); + Slog.d(TAG, mLogIdMsg + "Done with full transport backup."); } /** @@ -2711,13 +2731,11 @@ public class UserBackupManagerService { try { if (!mSetupComplete) { - Slog.i( - TAG, - addUserIdToLogMessage(mUserId, "Full restore not permitted before setup")); + Slog.i(TAG, mLogIdMsg + "Full restore not permitted before setup"); return; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning restore...")); + Slog.i(TAG, mLogIdMsg + "Beginning restore..."); AdbRestoreParams params = new AdbRestoreParams(fd); final int token = generateRandomIntegerToken(); @@ -2726,38 +2744,31 @@ public class UserBackupManagerService { } // start up the confirmation UI - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Starting restore confirmation UI, token=" + token)); + Slog.d(TAG, mLogIdMsg + "Starting restore confirmation UI, token=" + token); if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) { - Slog.e( - TAG, - addUserIdToLogMessage(mUserId, "Unable to launch restore confirmation")); + Slog.e(TAG, mLogIdMsg + "Unable to launch restore confirmation"); mAdbBackupRestoreConfirmations.delete(token); return; } // make sure the screen is lit for the user interaction - mPowerManager.userActivity(SystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_OTHER, - 0); + mPowerManager.userActivity( + SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0); // start the confirmation countdown startConfirmationTimeout(token, params); // wait for the restore to be performed - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Waiting for restore completion...")); + Slog.d(TAG, mLogIdMsg + "Waiting for restore completion..."); waitForCompletion(params); } finally { try { fd.close(); } catch (IOException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Error trying to close fd after adb restore: " + e)); + Slog.w(TAG, mLogIdMsg + "Error trying to close fd after adb restore: " + e); } Binder.restoreCallingIdentity(oldId); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "adb restore processing complete.")); + Slog.i(TAG, mLogIdMsg + "adb restore processing complete."); } } @@ -2766,13 +2777,14 @@ public class UserBackupManagerService { * to the backup agent during restore. */ public void excludeKeysFromRestore(String packageName, List<String> keys) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "excludeKeysFromRestore"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "excludeKeysFromRestore"); mBackupPreferences.addExcludedKeys(packageName, keys); } - public void reportDelayedRestoreResult(String packageName, - List<BackupRestoreEventLogger.DataTypeResult> results) { + /** See {@link BackupManager#reportDelayedRestoreResult(BackupRestoreEventLogger)}. */ + public void reportDelayedRestoreResult( + String packageName, List<BackupRestoreEventLogger.DataTypeResult> results) { String transport = mTransportManager.getCurrentTransportName(); if (transport == null) { Slog.w(TAG, "Failed to send delayed restore logs as no transport selected"); @@ -2781,26 +2793,34 @@ public class UserBackupManagerService { TransportConnection transportConnection = null; try { - PackageInfo packageInfo = getPackageManager().getPackageInfoAsUser(packageName, - PackageManager.PackageInfoFlags.of(/* value */ 0), getUserId()); + PackageInfo packageInfo = + getPackageManager() + .getPackageInfoAsUser( + packageName, + PackageManager.PackageInfoFlags.of(/* value */ 0), + getUserId()); - transportConnection = mTransportManager.getTransportClientOrThrow( - transport, /* caller */"BMS.reportDelayedRestoreResult"); - BackupTransportClient transportClient = transportConnection.connectOrThrow( - /* caller */ "BMS.reportDelayedRestoreResult"); + transportConnection = + mTransportManager.getTransportClientOrThrow( + transport, /* caller */ "BMS.reportDelayedRestoreResult"); + BackupTransportClient transportClient = + transportConnection.connectOrThrow( + /* caller */ "BMS.reportDelayedRestoreResult"); IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor(); - BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = + BackupManagerMonitorEventSender mBackupManagerMonitorEventSender = getBMMEventSender(monitor); - mBackupManagerMonitorEventSender.sendAgentLoggingResults(packageInfo, results, - BackupAnnotations.OperationType.RESTORE); - } catch (NameNotFoundException | TransportNotAvailableException - | TransportNotRegisteredException | RemoteException e) { + mBackupManagerMonitorEventSender.sendAgentLoggingResults( + packageInfo, results, BackupAnnotations.OperationType.RESTORE); + } catch (NameNotFoundException + | TransportNotAvailableException + | TransportNotRegisteredException + | RemoteException e) { Slog.w(TAG, "Failed to send delayed restore logs: " + e); } finally { if (transportConnection != null) { - mTransportManager.disposeOfTransportClient(transportConnection, - /* caller */"BMS.reportDelayedRestoreResult"); + mTransportManager.disposeOfTransportClient( + transportConnection, /* caller */ "BMS.reportDelayedRestoreResult"); } } } @@ -2808,7 +2828,8 @@ public class UserBackupManagerService { private boolean startConfirmationUi(int token, String action) { try { Intent confIntent = new Intent(action); - confIntent.setClassName("com.android.backupconfirm", + confIntent.setClassName( + "com.android.backupconfirm", "com.android.backupconfirm.BackupRestoreConfirmation"); confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token); confIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); @@ -2821,11 +2842,14 @@ public class UserBackupManagerService { private void startConfirmationTimeout(int token, AdbParams params) { if (DEBUG) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Posting conf timeout msg after " - + TIMEOUT_FULL_CONFIRMATION + " millis")); + Slog.d( + TAG, + mLogIdMsg + + "Posting conf timeout msg after " + + TIMEOUT_FULL_CONFIRMATION + + " millis"); } - Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, - token, 0, params); + Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, token, 0, params); mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION); } @@ -2834,7 +2858,9 @@ public class UserBackupManagerService { while (!params.latch.get()) { try { params.latch.wait(); - } catch (InterruptedException e) { /* never interrupted */ } + } catch (InterruptedException e) { + /* never interrupted */ + } } } } @@ -2851,15 +2877,20 @@ public class UserBackupManagerService { * Confirm that the previously-requested full backup/restore operation can proceed. This is used * to require a user-facing disclosure about the operation. */ - public void acknowledgeAdbBackupOrRestore(int token, boolean allow, - String curPassword, String encPpassword, IFullBackupRestoreObserver observer) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "acknowledgeAdbBackupOrRestore : token=" + token + " allow=" + allow)); + public void acknowledgeAdbBackupOrRestore( + int token, + boolean allow, + String curPassword, + String encPpassword, + IFullBackupRestoreObserver observer) { + Slog.d( + TAG, + mLogIdMsg + "acknowledgeAdbBackupOrRestore : token=" + token + " allow=" + allow); // TODO: possibly require not just this signature-only permission, but even // require that the specific designated confirmation-UI app uid is the caller? - mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, - "acknowledgeAdbBackupOrRestore"); + mContext.enforceCallingPermission( + android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore"); final long oldId = Binder.clearCallingIdentity(); try { @@ -2872,9 +2903,10 @@ public class UserBackupManagerService { mAdbBackupRestoreConfirmations.delete(token); if (allow) { - final int verb = params instanceof AdbBackupParams - ? MSG_RUN_ADB_BACKUP - : MSG_RUN_ADB_RESTORE; + final int verb = + params instanceof AdbBackupParams + ? MSG_RUN_ADB_BACKUP + : MSG_RUN_ADB_RESTORE; params.observer = observer; params.curPassword = curPassword; @@ -2882,28 +2914,20 @@ public class UserBackupManagerService { params.encryptPassword = encPpassword; if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "Sending conf message with verb " + verb)); + Slog.d(TAG, mLogIdMsg + "Sending conf message with verb " + verb); } mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(verb, params); mBackupHandler.sendMessage(msg); } else { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "User rejected full backup/restore operation")); + Slog.w(TAG, mLogIdMsg + "User rejected full backup/restore operation"); // indicate completion without having actually transferred any data signalAdbBackupRestoreCompletion(params); } } else { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Attempted to ack full backup/restore with invalid token")); + mLogIdMsg + "Attempted to ack full backup/restore with invalid token"); } } } finally { @@ -2922,10 +2946,10 @@ public class UserBackupManagerService { } private void setBackupEnabled(boolean enable, boolean persistToDisk) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setBackupEnabled"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "setBackupEnabled"); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup enabled => " + enable)); + Slog.i(TAG, mLogIdMsg + "Backup enabled => " + enable); final long oldId = Binder.clearCallingIdentity(); try { @@ -2944,23 +2968,25 @@ public class UserBackupManagerService { } synchronized void setFrameworkSchedulingEnabled(boolean isEnabled) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setFrameworkSchedulingEnabled"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "setFrameworkSchedulingEnabled"); boolean wasEnabled = isFrameworkSchedulingEnabled(); if (wasEnabled == isEnabled) { return; } - Slog.i(TAG, addUserIdToLogMessage(mUserId, - (isEnabled ? "Enabling" : "Disabling") + " backup scheduling")); + Slog.i(TAG, mLogIdMsg + (isEnabled ? "Enabling" : "Disabling") + " backup scheduling"); final long oldId = Binder.clearCallingIdentity(); try { // TODO(b/264889098): Consider at a later point if we should us a sentinel file as // setBackupEnabled. - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_SCHEDULING_ENABLED, isEnabled ? 1 : 0, mUserId); + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_SCHEDULING_ENABLED, + isEnabled ? 1 : 0, + mUserId); if (!isEnabled) { KeyValueBackupJob.cancel(mUserId, mContext); @@ -2977,8 +3003,12 @@ public class UserBackupManagerService { synchronized boolean isFrameworkSchedulingEnabled() { // By default scheduling is enabled final int defaultSetting = 1; - int isEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_SCHEDULING_ENABLED, defaultSetting, mUserId); + int isEnabled = + Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_SCHEDULING_ENABLED, + defaultSetting, + mUserId); return isEnabled == 1; } @@ -2992,7 +3022,7 @@ public class UserBackupManagerService { } else if (!enable) { // No longer enabled, so stop running backups if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Opting out of backup")); + Slog.i(TAG, mLogIdMsg + "Opting out of backup"); } KeyValueBackupJob.cancel(mUserId, mContext); @@ -3012,11 +3042,7 @@ public class UserBackupManagerService { dirName = mTransportManager.getTransportDirName(name); } catch (TransportNotRegisteredException e) { // Should never happen - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Unexpected unregistered transport"), - e); + Slog.e(TAG, mLogIdMsg + "Unexpected unregistered transport", e); return; } transportNames.add(name); @@ -3025,13 +3051,10 @@ public class UserBackupManagerService { // build the set of transports for which we are posting an init for (int i = 0; i < transportNames.size(); i++) { - recordInitPending( - true, - transportNames.get(i), - transportDirNames.get(i)); + recordInitPending(true, transportNames.get(i), transportDirNames.get(i)); } - mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), - mRunInitIntent); + mAlarmManager.set( + AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), mRunInitIntent); } } } @@ -3049,16 +3072,19 @@ public class UserBackupManagerService { /** Enable/disable automatic restore of app data at install time. */ public void setAutoRestore(boolean doAutoRestore) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setAutoRestore"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "setAutoRestore"); - Slog.i(TAG, addUserIdToLogMessage(mUserId, "Auto restore => " + doAutoRestore)); + Slog.i(TAG, mLogIdMsg + "Auto restore => " + doAutoRestore); final long oldId = Binder.clearCallingIdentity(); try { synchronized (this) { - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0, mUserId); + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_AUTO_RESTORE, + doAutoRestore ? 1 : 0, + mUserId); mAutoRestore = doAutoRestore; } } finally { @@ -3068,21 +3094,18 @@ public class UserBackupManagerService { /** Report whether the backup mechanism is currently enabled. */ public boolean isBackupEnabled() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "isBackupEnabled"); - return mEnabled; // no need to synchronize just to read it + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "isBackupEnabled"); + return mEnabled; // no need to synchronize just to read it } /** Report the name of the currently active transport. */ public String getCurrentTransport() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getCurrentTransport"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getCurrentTransport"); String currentTransport = mTransportManager.getCurrentTransportName(); if (DEBUG) { - Slog.v( - TAG, - addUserIdToLogMessage( - mUserId, "... getCurrentTransport() returning " + currentTransport)); + Slog.v(TAG, mLogIdMsg + "... getCurrentTransport() returning " + currentTransport); } return currentTransport; } @@ -3107,16 +3130,16 @@ public class UserBackupManagerService { /** Report all known, available backup transports by name. */ public String[] listAllTransports() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "listAllTransports"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "listAllTransports"); return mTransportManager.getRegisteredTransportNames(); } /** Report all known, available backup transports by component. */ public ComponentName[] listAllTransportComponents() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "listAllTransportComponents"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "listAllTransportComponents"); return mTransportManager.getRegisteredTransportComponents(); } @@ -3129,18 +3152,17 @@ public class UserBackupManagerService { * @param transportComponent The identity of the transport being described. * @param name A {@link String} with the new name for the transport. This is NOT for * identification. MUST NOT be {@code null}. - * @param configurationIntent An {@link Intent} that can be passed to - * {@link Context#startActivity} in order to launch the transport's configuration UI. It may - * be {@code null} if the transport does not offer any user-facing configuration UI. + * @param configurationIntent An {@link Intent} that can be passed to {@link + * Context#startActivity} in order to launch the transport's configuration UI. It may be + * {@code null} if the transport does not offer any user-facing configuration UI. * @param currentDestinationString A {@link String} describing the destination to which the * transport is currently sending data. MUST NOT be {@code null}. - * @param dataManagementIntent An {@link Intent} that can be passed to - * {@link Context#startActivity} in order to launch the transport's data-management UI. It - * may be {@code null} if the transport does not offer any user-facing data - * management UI. + * @param dataManagementIntent An {@link Intent} that can be passed to {@link + * Context#startActivity} in order to launch the transport's data-management UI. It may be + * {@code null} if the transport does not offer any user-facing data management UI. * @param dataManagementLabel A {@link CharSequence} to be used as the label for the transport's - * data management affordance. This MUST be {@code null} when dataManagementIntent is - * {@code null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. + * data management affordance. This MUST be {@code null} when dataManagementIntent is {@code + * null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. * @throws SecurityException If the UID of the calling process differs from the package UID of * {@code transportComponent} or if the caller does NOT have BACKUP permission. */ @@ -3175,8 +3197,7 @@ public class UserBackupManagerService { Objects.requireNonNull(transportComponent, "transportComponent can't be null"); Objects.requireNonNull(name, "name can't be null"); - Objects.requireNonNull( - currentDestinationString, "currentDestinationString can't be null"); + Objects.requireNonNull(currentDestinationString, "currentDestinationString can't be null"); Preconditions.checkArgument( (dataManagementIntent == null) == (dataManagementLabel == null), "dataManagementLabel should be null iff dataManagementIntent is null"); @@ -3211,7 +3232,7 @@ public class UserBackupManagerService { * selected transport. Returns {@code null} if the transport is not registered. * * @deprecated Use {@link #selectBackupTransportAsync(ComponentName, - * ISelectBackupTransportCallback)} instead. + * ISelectBackupTransportCallback)} instead. */ @Deprecated @Nullable @@ -3224,11 +3245,10 @@ public class UserBackupManagerService { if (!mTransportManager.isTransportRegistered(transportName)) { Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "Could not select transport " - + transportName - + ", as the transport is not registered.")); + mLogIdMsg + + "Could not select transport " + + transportName + + ", as the transport is not registered."); return null; } @@ -3236,12 +3256,11 @@ public class UserBackupManagerService { updateStateForTransport(transportName); Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "selectBackupTransport(transport = " - + transportName - + "): previous transport = " - + previousTransportName)); + mLogIdMsg + + "selectBackupTransport(transport = " + + transportName + + "): previous transport = " + + previousTransportName); return previousTransportName; } finally { Binder.restoreCallingIdentity(oldId); @@ -3262,9 +3281,7 @@ public class UserBackupManagerService { String transportString = transportComponent.flattenToShortString(); Slog.d( TAG, - addUserIdToLogMessage( - mUserId, - "selectBackupTransportAsync(transport = " + transportString + ")")); + mLogIdMsg + "selectBackupTransportAsync(transport = " + transportString + ")"); mBackupHandler.post( () -> { String transportName = null; @@ -3276,10 +3293,7 @@ public class UserBackupManagerService { mTransportManager.getTransportName(transportComponent); updateStateForTransport(transportName); } catch (TransportNotRegisteredException e) { - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Transport got unregistered")); + Slog.e(TAG, mLogIdMsg + "Transport got unregistered"); result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } } @@ -3293,10 +3307,9 @@ public class UserBackupManagerService { } catch (RemoteException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "ISelectBackupTransportCallback listener not" - + " available")); + mLogIdMsg + + "ISelectBackupTransportCallback listener not" + + " available"); } }); } finally { @@ -3306,8 +3319,11 @@ public class UserBackupManagerService { private void updateStateForTransport(String newTransportName) { // Publish the name change - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.BACKUP_TRANSPORT, newTransportName, mUserId); + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.BACKUP_TRANSPORT, + newTransportName, + mUserId); // And update our current-dataset bookkeeping String callerLogString = "BMS.updateStateForTransport()"; @@ -3315,8 +3331,8 @@ public class UserBackupManagerService { mTransportManager.getTransportClient(newTransportName, callerLogString); if (transportConnection != null) { try { - BackupTransportClient transport = transportConnection.connectOrThrow( - callerLogString); + BackupTransportClient transport = + transportConnection.connectOrThrow(callerLogString); mCurrentToken = transport.getCurrentRestoreSet(); } catch (Exception e) { // Oops. We can't know the current dataset token, so reset and figure it out @@ -3324,21 +3340,19 @@ public class UserBackupManagerService { mCurrentToken = 0; Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Transport " - + newTransportName - + " not available: current token = 0")); + mLogIdMsg + + "Transport " + + newTransportName + + " not available: current token = 0"); } mTransportManager.disposeOfTransportClient(transportConnection, callerLogString); } else { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Transport " - + newTransportName - + " not registered: current token = 0")); + mLogIdMsg + + "Transport " + + newTransportName + + " not registered: current token = 0"); // The named transport isn't registered, so we can't know what its current dataset token // is. Reset as above. mCurrentToken = 0; @@ -3351,24 +3365,20 @@ public class UserBackupManagerService { * returns {@code null}. */ public Intent getConfigurationIntent(String transportName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getConfigurationIntent"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getConfigurationIntent"); try { Intent intent = mTransportManager.getTransportConfigurationIntent(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getConfigurationIntent() returning intent " + intent)); + Slog.d(TAG, mLogIdMsg + "getConfigurationIntent() returning intent " + intent); } return intent; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get configuration intent from transport: " - + e.getMessage())); + mLogIdMsg + + "Unable to get configuration intent from transport: " + + e.getMessage()); return null; } } @@ -3389,42 +3399,36 @@ public class UserBackupManagerService { try { String string = mTransportManager.getTransportCurrentDestinationString(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getDestinationString() returning " + string)); + Slog.d(TAG, mLogIdMsg + "getDestinationString() returning " + string); } return string; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get destination string from transport: " + e.getMessage())); + mLogIdMsg + + "Unable to get destination string from transport: " + + e.getMessage()); return null; } } /** Supply the manage-data intent for the given transport. */ public Intent getDataManagementIntent(String transportName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getDataManagementIntent"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getDataManagementIntent"); try { Intent intent = mTransportManager.getTransportDataManagementIntent(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getDataManagementIntent() returning intent " + intent)); + Slog.d(TAG, mLogIdMsg + "getDataManagementIntent() returning intent " + intent); } return intent; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get management intent from transport: " + e.getMessage())); + mLogIdMsg + + "Unable to get management intent from transport: " + + e.getMessage()); return null; } } @@ -3434,24 +3438,19 @@ public class UserBackupManagerService { * transport. */ public CharSequence getDataManagementLabel(String transportName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "getDataManagementLabel"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BACKUP, "getDataManagementLabel"); try { CharSequence label = mTransportManager.getTransportDataManagementLabel(transportName); if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, "getDataManagementLabel() returning " + label)); + Slog.d(TAG, mLogIdMsg + "getDataManagementLabel() returning " + label); } return label; } catch (TransportNotRegisteredException e) { Slog.e( TAG, - addUserIdToLogMessage( - mUserId, - "Unable to get management label from transport: " + e.getMessage())); + mLogIdMsg + "Unable to get management label from transport: " + e.getMessage()); return null; } } @@ -3464,57 +3463,64 @@ public class UserBackupManagerService { if (Binder.getCallingUid() != Process.SYSTEM_UID) { Slog.w( TAG, - addUserIdToLogMessage( - mUserId, - "Non-system process uid=" - + Binder.getCallingUid() - + " attemping install-time restore")); + mLogIdMsg + + "Non-system process uid=" + + Binder.getCallingUid() + + " attemping install-time restore"); return; } boolean skip = false; long restoreSet = getAvailableRestoreToken(packageName); - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "restoreAtInstall pkg=" + packageName + " token=" + Integer.toHexString(token) - + " restoreSet=" + Long.toHexString(restoreSet))); + Slog.d( + TAG, + mLogIdMsg + + "restoreAtInstall pkg=" + + packageName + + " token=" + + Integer.toHexString(token) + + " restoreSet=" + + Long.toHexString(restoreSet)); if (restoreSet == 0) { - if (DEBUG) Slog.i(TAG, addUserIdToLogMessage(mUserId, "No restore set")); + if (DEBUG) Slog.i(TAG, mLogIdMsg + "No restore set"); skip = true; } - BackupManagerMonitorEventSender mBMMEventSender = - getBMMEventSender(/*monitor=*/ null); + BackupManagerMonitorEventSender mBMMEventSender = getBMMEventSender(/* monitor= */ null); PackageInfo packageInfo = getPackageInfoForBMMLogging(packageName); TransportConnection transportConnection = mTransportManager.getCurrentTransportClient("BMS.restoreAtInstall()"); if (transportConnection == null) { - Slog.w(TAG, addUserIdToLogMessage(mUserId, "No transport client")); + Slog.w(TAG, mLogIdMsg + "No transport client"); skip = true; } else if (Flags.enableIncreasedBmmLoggingForRestoreAtInstall()) { try { - BackupTransportClient transportClient = transportConnection.connectOrThrow( - "BMS.restoreAtInstall"); + BackupTransportClient transportClient = + transportConnection.connectOrThrow("BMS.restoreAtInstall"); mBMMEventSender.setMonitor(transportClient.getBackupManagerMonitor()); } catch (TransportNotAvailableException | RemoteException e) { mBMMEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, packageInfo, - BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); + BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, + packageInfo, + BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, + null); } } if (Flags.enableIncreasedBmmLoggingForRestoreAtInstall()) { mBMMEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_RESTORE_AT_INSTALL_INVOKED, packageInfo, + BackupManagerMonitor.LOG_EVENT_ID_RESTORE_AT_INSTALL_INVOKED, + packageInfo, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - mBMMEventSender.putMonitoringExtra(/*extras=*/null, + mBMMEventSender.putMonitoringExtra( + /* extras= */ null, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, BackupAnnotations.OperationType.RESTORE)); } if (!mAutoRestore) { - Slog.w(TAG, - addUserIdToLogMessage(mUserId, "Non-restorable state: auto=" + mAutoRestore)); + Slog.w(TAG, mLogIdMsg + "Non-restorable state: auto=" + mAutoRestore); skip = true; } @@ -3526,15 +3532,14 @@ public class UserBackupManagerService { mWakelock.acquire(); - OnTaskFinishedListener listener = caller -> { - mTransportManager.disposeOfTransportClient(transportConnection, caller); - mWakelock.release(); - }; + OnTaskFinishedListener listener = + caller -> { + mTransportManager.disposeOfTransportClient(transportConnection, caller); + mWakelock.release(); + }; if (DEBUG) { - Slog.d( - TAG, - addUserIdToLogMessage(mUserId, "Restore at install of " + packageName)); + Slog.d(TAG, mLogIdMsg + "Restore at install of " + packageName); } Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); msg.obj = @@ -3550,10 +3555,7 @@ public class UserBackupManagerService { mBackupHandler.sendMessage(msg); } catch (Exception e) { // Calling into the transport broke; back off and proceed with the installation. - Slog.e( - TAG, - addUserIdToLogMessage( - mUserId, "Unable to contact transport: " + e.getMessage())); + Slog.e(TAG, mLogIdMsg + "Unable to contact transport: " + e.getMessage()); skip = true; } } @@ -3563,9 +3565,11 @@ public class UserBackupManagerService { if (Flags.enableIncreasedBmmLoggingForRestoreAtInstall()) { mBMMEventSender.monitorEvent( - BackupManagerMonitor.LOG_EVENT_ID_SKIP_RESTORE_AT_INSTALL, packageInfo, + BackupManagerMonitor.LOG_EVENT_ID_SKIP_RESTORE_AT_INSTALL, + packageInfo, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - mBMMEventSender.putMonitoringExtra(/*extras=*/null, + mBMMEventSender.putMonitoringExtra( + /* extras= */ null, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, BackupAnnotations.OperationType.RESTORE)); } @@ -3576,10 +3580,12 @@ public class UserBackupManagerService { } // Tell the PackageManager to proceed with the post-install handling for this package. - Slog.d(TAG, addUserIdToLogMessage(mUserId, "Finishing install immediately")); + Slog.d(TAG, mLogIdMsg + "Finishing install immediately"); try { mPackageManagerBinder.finishPackageInstall(token, false); - } catch (RemoteException e) { /* can't happen */ } + } catch (RemoteException e) { + /* can't happen */ + } } } @@ -3592,8 +3598,9 @@ public class UserBackupManagerService { /** Hand off a restore session. */ public IRestoreSession beginRestoreSession(String packageName, String transport) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "beginRestoreSession: pkg=" + packageName + " transport=" + transport)); + Slog.d( + TAG, + mLogIdMsg + "beginRestoreSession: pkg=" + packageName + " transport=" + transport); boolean needPermission = true; if (transport == null) { @@ -3604,10 +3611,7 @@ public class UserBackupManagerService { try { app = mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); } catch (NameNotFoundException nnf) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Asked to restore nonexistent pkg " + packageName)); + Slog.w(TAG, mLogIdMsg + "Asked to restore nonexistent pkg " + packageName); throw new IllegalArgumentException("Package " + packageName + " not found"); } @@ -3624,46 +3628,45 @@ public class UserBackupManagerService { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "beginRestoreSession"); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "restoring self on current transport; no permission needed")); + Slog.d(TAG, mLogIdMsg + "restoring self on current transport; no permission needed"); } int backupDestination; TransportConnection transportConnection = null; try { - transportConnection = mTransportManager.getTransportClientOrThrow( - transport, /* caller */"BMS.beginRestoreSession"); + transportConnection = + mTransportManager.getTransportClientOrThrow( + transport, /* caller */ "BMS.beginRestoreSession"); backupDestination = getBackupDestinationFromTransport(transportConnection); - } catch (TransportNotAvailableException | TransportNotRegisteredException + } catch (TransportNotAvailableException + | TransportNotRegisteredException | RemoteException e) { Slog.w(TAG, "Failed to get operation type from transport: " + e); return null; } finally { if (transportConnection != null) { - mTransportManager.disposeOfTransportClient(transportConnection, - /* caller */"BMS.beginRestoreSession"); + mTransportManager.disposeOfTransportClient( + transportConnection, /* caller */ "BMS.beginRestoreSession"); } } synchronized (this) { if (mActiveRestoreSession != null) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, "Restore session requested but one already active")); + Slog.i(TAG, mLogIdMsg + "Restore session requested but one already active"); return null; } if (mBackupRunning) { - Slog.i( - TAG, - addUserIdToLogMessage( - mUserId, - "Restore session requested but currently running backups")); + Slog.i(TAG, mLogIdMsg + "Restore session requested but currently running backups"); return null; } - mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport, - getEligibilityRulesForOperation(backupDestination)); - mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT, + mActiveRestoreSession = + new ActiveRestoreSession( + this, + packageName, + transport, + getEligibilityRulesForOperation(backupDestination)); + mBackupHandler.sendEmptyMessageDelayed( + MSG_RESTORE_SESSION_TIMEOUT, mAgentTimeoutParameters.getRestoreSessionTimeoutMillis()); } return mActiveRestoreSession; @@ -3673,10 +3676,9 @@ public class UserBackupManagerService { public void clearRestoreSession(ActiveRestoreSession currentSession) { synchronized (this) { if (currentSession != mActiveRestoreSession) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "ending non-current restore session")); + Slog.e(TAG, mLogIdMsg + "ending non-current restore session"); } else { - Slog.d(TAG, addUserIdToLogMessage(mUserId, - "Clearing restore session and halting timeout")); + Slog.d(TAG, mLogIdMsg + "Clearing restore session and halting timeout"); mActiveRestoreSession = null; mBackupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT); } @@ -3688,11 +3690,14 @@ public class UserBackupManagerService { * outstanding asynchronous backup/restore operation. */ public void opComplete(int token, long result) { - mOperationStorage.onOperationComplete(token, result, callback -> { - Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result); - Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult); - mBackupHandler.sendMessage(msg); - }); + mOperationStorage.onOperationComplete( + token, + result, + callback -> { + Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result); + Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult); + mBackupHandler.sendMessage(msg); + }); } /** Checks if the package is eligible for backup. */ @@ -3748,10 +3753,16 @@ public class UserBackupManagerService { return getEligibilityRules(mPackageManager, mUserId, mContext, backupDestination); } - private static BackupEligibilityRules getEligibilityRules(PackageManager packageManager, - int userId, Context context, @BackupDestination int backupDestination) { - return new BackupEligibilityRules(packageManager, - LocalServices.getService(PackageManagerInternal.class), userId, context, + private static BackupEligibilityRules getEligibilityRules( + PackageManager packageManager, + int userId, + Context context, + @BackupDestination int backupDestination) { + return new BackupEligibilityRules( + packageManager, + LocalServices.getService(PackageManagerInternal.class), + userId, + context, backupDestination); } @@ -3793,20 +3804,20 @@ public class UserBackupManagerService { } private void dumpBMMEvents(PrintWriter pw) { - BackupManagerMonitorDumpsysUtils bm = - new BackupManagerMonitorDumpsysUtils(); + BackupManagerMonitorDumpsysUtils bm = new BackupManagerMonitorDumpsysUtils(); if (bm.deleteExpiredBMMEvents()) { pw.println("BACKUP MANAGER MONITOR EVENTS HAVE EXPIRED"); return; } File events = bm.getBMMEventsFile(); - if (events.length() == 0){ + if (events.length() == 0) { // We have not recorded BMMEvents yet. pw.println("NO BACKUP MANAGER MONITOR EVENTS"); return; - } else if (bm.isFileLargerThanSizeLimit(events)){ - pw.println("BACKUP MANAGER MONITOR EVENTS FILE OVER SIZE LIMIT - " - + "future events will not be recorded"); + } else if (bm.isFileLargerThanSizeLimit(events)) { + pw.println( + "BACKUP MANAGER MONITOR EVENTS FILE OVER SIZE LIMIT - " + + "future events will not be recorded"); } pw.println("START OF BACKUP MANAGER MONITOR EVENTS"); try (BufferedReader reader = new BufferedReader(new FileReader(events))) { @@ -3826,16 +3837,27 @@ public class UserBackupManagerService { // Add prefix for only non-system users so that system user dumpsys is the same as before String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":"; synchronized (mQueueLock) { - pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled") - + " / " + (!mSetupComplete ? "not " : "") + "setup complete / " - + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init"); + pw.println( + userPrefix + + "Backup Manager is " + + (mEnabled ? "enabled" : "disabled") + + " / " + + (!mSetupComplete ? "not " : "") + + "setup complete / " + + (this.mPendingInits.size() == 0 ? "not " : "") + + "pending init"); pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled")); if (mBackupRunning) pw.println("Backup currently running"); pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running"); - pw.println("Framework scheduling is " - + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled")); - pw.println("Last backup pass started: " + mLastBackupPass - + " (now = " + System.currentTimeMillis() + ')'); + pw.println( + "Framework scheduling is " + + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled")); + pw.println( + "Last backup pass started: " + + mLastBackupPass + + " (now = " + + System.currentTimeMillis() + + ')'); pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId)); pw.println(userPrefix + "Transport whitelist:"); @@ -3848,21 +3870,27 @@ public class UserBackupManagerService { final String[] transports = listAllTransports(); if (transports != null) { for (String t : transports) { - pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * " - : " ") + t); + pw.println( + (t.equals(mTransportManager.getCurrentTransportName()) + ? " * " + : " ") + + t); try { - File dir = new File(mBaseStateDir, - mTransportManager.getTransportDirName(t)); - pw.println(" destination: " - + mTransportManager.getTransportCurrentDestinationString(t)); - pw.println(" intent: " - + mTransportManager.getTransportConfigurationIntent(t)); + File dir = + new File(mBaseStateDir, mTransportManager.getTransportDirName(t)); + pw.println( + " destination: " + + mTransportManager.getTransportCurrentDestinationString( + t)); + pw.println( + " intent: " + + mTransportManager.getTransportConfigurationIntent(t)); for (File f : dir.listFiles()) { pw.println( " " + f.getName() + " - " + f.length() + " state bytes"); } } catch (Exception e) { - Slog.e(TAG, addUserIdToLogMessage(mUserId, "Error in transport"), e); + Slog.e(TAG, mLogIdMsg + "Error in transport", e); pw.println(" Error: " + e); } } @@ -3892,8 +3920,10 @@ public class UserBackupManagerService { } } - pw.println(userPrefix + "Ancestral packages: " - + (mAncestralPackages == null ? "none" : mAncestralPackages.size())); + pw.println( + userPrefix + + "Ancestral packages: " + + (mAncestralPackages == null ? "none" : mAncestralPackages.size())); if (mAncestralPackages != null) { for (String pkg : mAncestralPackages) { pw.println(" " + pkg); @@ -3919,29 +3949,35 @@ public class UserBackupManagerService { pw.println(entry.packageName); } pw.println(userPrefix + "Agent timeouts:"); - pw.println(" KvBackupAgentTimeoutMillis: " - + mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis()); - pw.println(" FullBackupAgentTimeoutMillis: " - + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis()); - pw.println(" SharedBackupAgentTimeoutMillis: " - + mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis()); - pw.println(" RestoreAgentTimeoutMillis (system): " - + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( - Process.FIRST_APPLICATION_UID - 1)); - pw.println(" RestoreAgentTimeoutMillis: " - + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( - Process.FIRST_APPLICATION_UID)); - pw.println(" RestoreAgentFinishedTimeoutMillis: " - + mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis()); - pw.println(" QuotaExceededTimeoutMillis: " - + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis()); - + pw.println( + " KvBackupAgentTimeoutMillis: " + + mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis()); + pw.println( + " FullBackupAgentTimeoutMillis: " + + mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis()); + pw.println( + " SharedBackupAgentTimeoutMillis: " + + mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis()); + pw.println( + " RestoreAgentTimeoutMillis (system): " + + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( + Process.FIRST_APPLICATION_UID - 1)); + pw.println( + " RestoreAgentTimeoutMillis: " + + mAgentTimeoutParameters.getRestoreAgentTimeoutMillis( + Process.FIRST_APPLICATION_UID)); + pw.println( + " RestoreAgentFinishedTimeoutMillis: " + + mAgentTimeoutParameters.getRestoreAgentFinishedTimeoutMillis()); + pw.println( + " QuotaExceededTimeoutMillis: " + + mAgentTimeoutParameters.getQuotaExceededTimeoutMillis()); } } @VisibleForTesting - @BackupDestination int getBackupDestinationFromTransport( - TransportConnection transportConnection) + @BackupDestination + int getBackupDestinationFromTransport(TransportConnection transportConnection) throws TransportNotAvailableException, RemoteException { if (!shouldUseNewBackupEligibilityRules()) { // Return the default to stick to the legacy behaviour. @@ -3950,8 +3986,9 @@ public class UserBackupManagerService { final long oldCallingId = Binder.clearCallingIdentity(); try { - BackupTransportClient transport = transportConnection.connectOrThrow( - /* caller */ "BMS.getBackupDestinationFromTransport"); + BackupTransportClient transport = + transportConnection.connectOrThrow( + /* caller */ "BMS.getBackupDestinationFromTransport"); if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) { return BackupDestination.DEVICE_TRANSFER; } else { @@ -3964,15 +4001,10 @@ public class UserBackupManagerService { @VisibleForTesting boolean shouldUseNewBackupEligibilityRules() { - return FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES); + return FeatureFlagUtils.isEnabled( + mContext, FeatureFlagUtils.SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES); } - private static String addUserIdToLogMessage(int userId, String message) { - return "[UserID:" + userId + "] " + message; - } - - public IBackupManager getBackupManagerBinder() { return mBackupManagerBinder; } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 0e2e50589217..a37b2b926c9c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -612,7 +612,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void enablePermissionsSync(int associationId) { - if (getCallingUid() != SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } mSystemDataTransferProcessor.enablePermissionsSync(associationId); @@ -620,7 +620,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void disablePermissionsSync(int associationId) { - if (getCallingUid() != SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } mSystemDataTransferProcessor.disablePermissionsSync(associationId); @@ -628,7 +628,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - if (getCallingUid() != SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); @@ -704,7 +704,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public byte[] getBackupPayload(int userId) { - if (getCallingUid() != SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { throw new SecurityException("Caller must be system"); } return mBackupRestoreProcessor.getBackupPayload(userId); @@ -712,7 +712,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void applyRestoredPayload(byte[] payload, int userId) { - if (getCallingUid() != SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { throw new SecurityException("Caller must be system"); } mBackupRestoreProcessor.applyRestoredPayload(payload, userId); diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index f401e6b66093..c385fbad02a5 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -490,7 +490,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub ? mParams.getBlockedActivities() : mParams.getAllowedActivities()); - if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { + if (mParams.getInputMethodComponent() != null) { final String imeId = mParams.getInputMethodComponent().flattenToShortString(); Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device " + deviceId); @@ -807,7 +807,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } // Clear any previously set custom IME components. - if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { + if (mParams.getInputMethodComponent() != null) { InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers( mDeviceId, null); } diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index abfb8268bd9a..df47c98d6433 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -98,6 +98,8 @@ public class ContextualSearchManagerService extends SystemService { private static final int MSG_INVALIDATE_TOKEN = 1; private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes + private static final boolean DEBUG = false; + private final Context mContext; private final ActivityTaskManagerInternal mAtmInternal; private final PackageManagerInternal mPackageManager; @@ -121,6 +123,7 @@ public class ContextualSearchManagerService extends SystemService { final Bundle data, final int activityIndex, final int activityCount) { + final IContextualSearchCallback callback; synchronized (mLock) { callback = mStateCallback; @@ -160,7 +163,7 @@ public class ContextualSearchManagerService extends SystemService { public ContextualSearchManagerService(@NonNull Context context) { super(context); - if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created"); + if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created"); mContext = context; mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); @@ -206,7 +209,7 @@ public class ContextualSearchManagerService extends SystemService { mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE); mTemporaryHandler = null; } - if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset."); + if (DEBUG) Log.d(TAG, "mTemporaryPackage reset."); mTemporaryPackage = null; updateSecureSetting(); } @@ -239,7 +242,7 @@ public class ContextualSearchManagerService extends SystemService { mTemporaryPackage = temporaryPackage; updateSecureSetting(); mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs); - if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage); + if (DEBUG) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage); } } @@ -256,7 +259,7 @@ public class ContextualSearchManagerService extends SystemService { + durationMs + ")"); } mTokenValidDurationMs = durationMs; - if (DEBUG_USER) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs); + if (DEBUG) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs); } } @@ -268,12 +271,12 @@ public class ContextualSearchManagerService extends SystemService { private Intent getResolvedLaunchIntent(int userId) { synchronized (this) { - if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent"); + if(DEBUG) Log.d(TAG, "Attempting to getResolvedLaunchIntent"); // If mTemporaryPackage is not null, use it to get the ContextualSearch intent. String csPkgName = getContextualSearchPackageName(); if (csPkgName.isEmpty()) { // Return null if csPackageName is not specified. - if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty"); + if (DEBUG) Log.w(TAG, "getContextualSearchPackageName is empty"); return null; } Intent launchIntent = new Intent( @@ -282,12 +285,12 @@ public class ContextualSearchManagerService extends SystemService { ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser( launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); if (resolveInfo == null) { - if (DEBUG_USER) Log.w(TAG, "resolveInfo is null"); + if (DEBUG) Log.w(TAG, "resolveInfo is null"); return null; } ComponentName componentName = resolveInfo.getComponentInfo().getComponentName(); if (componentName == null) { - if (DEBUG_USER) Log.w(TAG, "componentName is null"); + if (DEBUG) Log.w(TAG, "componentName is null"); return null; } launchIntent.setComponent(componentName); @@ -298,11 +301,11 @@ public class ContextualSearchManagerService extends SystemService { private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) { final Intent launchIntent = getResolvedLaunchIntent(userId); if (launchIntent == null) { - if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null"); + if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null"); return null; } - if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent()); + if (DEBUG) Log.d(TAG, "Launch component: " + launchIntent.getComponent()); launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION | FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK); launchIntent.putExtra( @@ -355,7 +358,7 @@ public class ContextualSearchManagerService extends SystemService { TYPE_NAVIGATION_BAR_PANEL, TYPE_POINTER)); } else { - if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null"); + if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null"); shb = null; } final Bitmap bm = shb != null ? shb.asBitmap() : null; @@ -429,7 +432,7 @@ public class ContextualSearchManagerService extends SystemService { mTokenHandler.removeMessages(MSG_INVALIDATE_TOKEN); mTokenHandler = null; } - if (DEBUG_USER) Log.d(TAG, "mToken invalidated."); + if (DEBUG) Log.d(TAG, "mToken invalidated."); mToken = null; } } @@ -459,7 +462,7 @@ public class ContextualSearchManagerService extends SystemService { @Override public void startContextualSearch(int entrypoint) { synchronized (this) { - if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint); + if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint); enforcePermission("startContextualSearch"); final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); @@ -474,7 +477,7 @@ public class ContextualSearchManagerService extends SystemService { getContextualSearchIntent(entrypoint, callingUserId, mToken); if (launchIntent != null) { int result = invokeContextualSearchIntent(launchIntent, callingUserId); - if (DEBUG_USER) Log.d(TAG, "Launch result: " + result); + if (DEBUG) Log.d(TAG, "Launch result: " + result); } }); } @@ -484,11 +487,11 @@ public class ContextualSearchManagerService extends SystemService { public void getContextualSearchState( @NonNull IBinder token, @NonNull IContextualSearchCallback callback) { - if (DEBUG_USER) { + if (DEBUG) { Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback); } if (mToken == null || !mToken.getToken().equals(token)) { - if (DEBUG_USER) { + if (DEBUG) { Log.e(TAG, "getContextualSearchState: invalid token, returning error"); } try { diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index 1588e0421675..7a5b8660ef7c 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -40,7 +40,9 @@ import android.util.AtomicFile; import android.util.EventLog; import android.util.Slog; import android.util.Xml; +import android.util.proto.ProtoFieldFilter; import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoParseException; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -49,10 +51,13 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.am.DropboxRateLimiter; +import com.android.server.os.TombstoneProtos.Tombstone; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; @@ -64,6 +69,7 @@ import java.nio.file.Files; import java.nio.file.attribute.PosixFilePermissions; import java.util.HashMap; import java.util.Iterator; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -392,6 +398,129 @@ public class BootReceiver extends BroadcastReceiver { writeTimestamps(timestamps); } + /** + * Processes a tombstone file and adds it to the DropBox after filtering and applying + * rate limiting. + * Filtering removes memory sections from the tombstone proto to reduce size while preserving + * critical information. The filtered tombstone is then added to DropBox in both proto + * and text formats, with the text format derived from the filtered proto. + * Rate limiting is applied as it is the case with other crash types. + * + * @param ctx Context + * @param tombstone path to the tombstone + * @param processName the name of the process corresponding to the tombstone + * @param tmpFileLock the lock for reading/writing tmp files + */ + public static void filterAndAddTombstoneToDropBox( + Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) { + final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); + if (db == null) { + Slog.e(TAG, "Can't log tombstone: DropBoxManager not available"); + return; + } + File filteredProto = null; + // Check if we should rate limit and abort early if needed. + DropboxRateLimiter.RateLimitResult rateLimitResult = + sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName); + if (rateLimitResult.shouldRateLimit()) return; + + HashMap<String, Long> timestamps = readTimestamps(); + try { + tmpFileLock.lock(); + Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName()); + // Create a temporary tombstone without memory sections. + filteredProto = createTempTombstoneWithoutMemory(tombstone); + Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName()); + + if (recordFileTimestamp(tombstone, timestamps)) { + // We need to attach the count indicating the number of dropped dropbox entries + // due to rate limiting. Do this by enclosing the proto tombsstone in a + // container proto that has the dropped entry count and the proto tombstone as + // bytes (to avoid the complexity of reading and writing nested protos). + Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox"); + addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult); + } + // Always add the text version of the tombstone to the DropBox, in order to + // match the previous behaviour. + Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName() + + " to dropbox"); + addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult); + + } catch (IOException | ProtoParseException e) { + Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName() + + "' to DropBox. Error during processing or writing: " + e.getMessage(), e); + } finally { + if (filteredProto != null) { + filteredProto.delete(); + } + tmpFileLock.unlock(); + } + writeTimestamps(timestamps); + } + + /** + * Creates a temporary tombstone file by filtering out memory mapping fields. + * This ensures that the unneeded memory mapping data is removed from the tombstone + * before adding it to Dropbox + * + * @param tombstone the original tombstone file to process + * @return a temporary file containing the filtered tombstone data + * @throws IOException if an I/O error occurs during processing + */ + private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException { + // Process the proto tombstone file and write it to a temporary file + File tombstoneProto = + File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR); + ProtoFieldFilter protoFilter = + new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS); + + try (FileInputStream fis = new FileInputStream(tombstone); + BufferedInputStream bis = new BufferedInputStream(fis); + FileOutputStream fos = new FileOutputStream(tombstoneProto); + BufferedOutputStream bos = new BufferedOutputStream(fos)) { + protoFilter.filter(bis, bos); + return tombstoneProto; + } + } + + private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db, + HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) { + File tombstoneTextFile = null; + + try { + tombstoneTextFile = File.createTempFile(tombstone.getName(), + ".pb.txt.tmp", TOMBSTONE_TMP_DIR); + + // Create a ProcessBuilder to execute pbtombstone + ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath()); + pb.redirectOutput(tombstoneTextFile); + Process process = pb.start(); + + // Wait 10 seconds for the process to complete + if (!process.waitFor(10, TimeUnit.SECONDS)) { + Slog.e(TAG, "pbtombstone timed out"); + process.destroyForcibly(); + return; + } + + int exitCode = process.exitValue(); + if (exitCode != 0) { + Slog.e(TAG, "pbtombstone failed with exit code " + exitCode); + } else { + final String headers = getBootHeadersToLogAndUpdate() + + rateLimitResult.createHeader(); + addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE, + TAG_TOMBSTONE); + } + } catch (IOException | InterruptedException e) { + Slog.e(TAG, "Failed to process tombstone with pbtombstone", e); + } finally { + if (tombstoneTextFile != null) { + tombstoneTextFile.delete(); + } + } + } + private static void addAugmentedProtoToDropbox( File tombstone, DropBoxManager db, DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 2216f2769826..f7eaa159be30 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2553,13 +2553,12 @@ public final class ProcessList { final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app); // We can't isolate app data and storage data as parent zygote already did that. - startResult = appZygote.getProcess().start(entryPoint, - app.processName, uid, uid, gids, runtimeFlags, mountExternal, + startResult = appZygote.startProcess(entryPoint, + app.processName, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, - app.info.dataDir, null, app.info.packageName, - /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp, - app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap, - false, false, false, + app.info.dataDir, app.info.packageName, isTopApp, + app.getDisabledCompatChanges(), pkgDataInfoMap, + allowlistedAppDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); } else { regularZygote = true; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d76c04ac7f31..27e9e44f1090 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -4062,8 +4062,7 @@ class UserController implements Handler.Callback { synchronized (mUserSwitchingDialogLock) { dismissUserSwitchingDialog(null); mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, - switchingFromSystemUserMessage, switchingToSystemUserMessage, - getWindowManager()); + switchingFromSystemUserMessage, switchingToSystemUserMessage); mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 2d7456471be4..d1fcb9d1ca37 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -39,7 +39,6 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.util.Slog; import android.util.TypedValue; import android.view.View; @@ -53,7 +52,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.util.ObjectUtils; import com.android.internal.util.UserIcons; -import com.android.server.wm.WindowManagerService; import java.util.concurrent.atomic.AtomicBoolean; @@ -80,14 +78,11 @@ class UserSwitchingDialog extends Dialog { protected final UserInfo mNewUser; private final String mSwitchingFromSystemUserMessage; private final String mSwitchingToSystemUserMessage; - private final WindowManagerService mWindowManager; protected final Context mContext; private final int mTraceCookie; - private final boolean mNeedToFreezeScreen; UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage, - WindowManagerService windowManager) { + String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { super(context, R.style.Theme_Material_NoActionBar_Fullscreen); mContext = context; @@ -97,8 +92,6 @@ class UserSwitchingDialog extends Dialog { mSwitchingToSystemUserMessage = switchingToSystemUserMessage; mDisableAnimations = SystemProperties.getBoolean( "debug.usercontroller.disable_user_switching_dialog_animations", false); - mWindowManager = windowManager; - mNeedToFreezeScreen = !mDisableAnimations && !isUserSetupComplete(newUser); mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id; inflateContent(); @@ -183,11 +176,6 @@ class UserSwitchingDialog extends Dialog { : res.getString(R.string.user_switching_message, mNewUser.name); } - private boolean isUserSetupComplete(UserInfo user) { - return Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0, user.id) == 1; - } - @Override public void show() { asyncTraceBegin("dialog", 0); @@ -197,7 +185,6 @@ class UserSwitchingDialog extends Dialog { @Override public void dismiss() { super.dismiss(); - stopFreezingScreen(); asyncTraceEnd("dialog", 0); } @@ -205,7 +192,6 @@ class UserSwitchingDialog extends Dialog { if (DEBUG) Slog.d(TAG, "show called"); show(); startShowAnimation(() -> { - startFreezingScreen(); onShown.run(); }); } @@ -223,24 +209,6 @@ class UserSwitchingDialog extends Dialog { } } - private void startFreezingScreen() { - if (!mNeedToFreezeScreen) { - return; - } - traceBegin("startFreezingScreen"); - mWindowManager.startFreezingScreen(0, 0); - traceEnd("startFreezingScreen"); - } - - private void stopFreezingScreen() { - if (!mNeedToFreezeScreen) { - return; - } - traceBegin("stopFreezingScreen"); - mWindowManager.stopFreezingScreen(); - traceEnd("stopFreezingScreen"); - } - private void startShowAnimation(Runnable onAnimationEnd) { if (mDisableAnimations) { onAnimationEnd.run(); @@ -260,7 +228,7 @@ class UserSwitchingDialog extends Dialog { } private void startDismissAnimation(Runnable onAnimationEnd) { - if (mDisableAnimations || mNeedToFreezeScreen) { + if (mDisableAnimations) { // animations are disabled or screen is frozen, no need to play an animation onAnimationEnd.run(); return; @@ -352,14 +320,4 @@ class UserSwitchingDialog extends Dialog { Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie); if (DEBUG) Slog.d(TAG, "asyncTraceEnd-" + subTag); } - - private void traceBegin(String msg) { - if (DEBUG) Slog.d(TAG, "traceBegin-" + msg); - Trace.traceBegin(TRACE_TAG, msg); - } - - private void traceEnd(String msg) { - Trace.traceEnd(TRACE_TAG); - if (DEBUG) Slog.d(TAG, "traceEnd-" + msg); - } } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index ba391d0a9995..5aa2a6b60106 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -205,18 +205,8 @@ final class HistoricalRegistry { mContext = context; if (Flags.enableSqliteAppopsAccesses()) { mDiscreteRegistry = new DiscreteOpsSqlRegistry(context); - if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) { - DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry; - DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context); - DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); - } } else { mDiscreteRegistry = new DiscreteOpsXmlRegistry(context); - if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite - DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context); - DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry; - DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); - } } } @@ -267,6 +257,19 @@ final class HistoricalRegistry { } } } + if (Flags.enableSqliteAppopsAccesses()) { + if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) { + DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry; + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mContext); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); + } + } else { + if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext); + DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry; + DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); + } + } } private boolean isPersistenceInitializedMLocked() { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b9b06701a11b..c125d2d2e8cd 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -256,6 +256,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.modules.expresslog.Counter; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; @@ -838,9 +839,49 @@ public class AudioService extends IAudioService.Stub new AudioServiceUserRestrictionsListener(); private final IAudioManagerNative mNativeShim = new IAudioManagerNative.Stub() { + static final String METRIC_COUNTERS_PLAYBACK_PARTIAL = + "media_audio.value_audio_playback_hardening_partial_restriction"; + static final String METRIC_COUNTERS_PLAYBACK_STRICT = + "media_audio.value_audio_playback_hardening_strict_would_restrict"; + + String getPackNameForUid(int uid) { + final long token = Binder.clearCallingIdentity(); + try { + final String[] names = AudioService.this.mContext. + getPackageManager().getPackagesForUid(uid); + if (names == null + || names.length == 0 + || TextUtils.isEmpty(names[0])) { + return "[" + uid + "]"; + } + return names[0]; + } finally { + Binder.restoreCallingIdentity(token); + } + } + // oneway @Override public void playbackHardeningEvent(int uid, byte type, boolean bypassed) { + if (Binder.getCallingUid() != Process.AUDIOSERVER_UID) { + return; + } + if (type == HardeningType.PARTIAL) { + Counter.logIncrementWithUid(METRIC_COUNTERS_PLAYBACK_PARTIAL, uid); + } else if (type == HardeningType.FULL) { + Counter.logIncrementWithUid(METRIC_COUNTERS_PLAYBACK_STRICT, uid); + } else { + Slog.wtf(TAG, "Unexpected hardening type" + type); + return; + } + String msg = "AudioHardening background playback " + + (bypassed ? "would be " : "") + + "muted for " + + getPackNameForUid(uid) + " (" + uid + "), " + + "level: " + (type == HardeningType.PARTIAL ? "partial" : "full"); + + AudioService.this.mHardeningLogger.enqueueAndSlog(msg, + bypassed ? EventLogger.Event.ALOGI : EventLogger.Event.ALOGW, TAG); } @Override @@ -1544,7 +1585,8 @@ public class AudioService extends IAudioService.Stub mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive(), mAppOps, - context.getPackageManager()); + context.getPackageManager(), + mHardeningLogger); } private void initVolumeStreamStates() { @@ -12691,6 +12733,7 @@ public class AudioService extends IAudioService.Stub static final int LOG_NB_EVENTS_DYN_POLICY = 10; static final int LOG_NB_EVENTS_SPATIAL = 30; static final int LOG_NB_EVENTS_SOUND_DOSE = 50; + static final int LOG_NB_EVENTS_HARDENING = 50; static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30; @@ -12729,6 +12772,9 @@ public class AudioService extends IAudioService.Stub mDynPolicyLogger = new EventLogger(LOG_NB_EVENTS_DYN_POLICY, "dynamic policy events (logged when command received by AudioService)"); + private final EventLogger mHardeningLogger = new EventLogger( + LOG_NB_EVENTS_HARDENING, "Hardening enforcement"); + private static final String[] RINGER_MODE_NAMES = new String[] { "SILENT", "VIBRATE", @@ -12803,7 +12849,7 @@ public class AudioService extends IAudioService.Stub pw.println("\nMessage handler is null"); } dumpFlags(pw); - mHardeningEnforcer.dump(pw); + mHardeningLogger.dump(pw); mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpVolumeGroups(pw); diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java index 661111070aae..f69a810b314f 100644 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -54,8 +54,7 @@ public class HardeningEnforcer { final ActivityManager mActivityManager; final PackageManager mPackageManager; - final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS, - "Hardening enforcement"); + final EventLogger mEventLogger; // capacity = 4 for each of the focus request types static final SparseArray<String> METRIC_COUNTERS_FOCUS_DENIAL = new SparseArray<>(4); @@ -108,17 +107,13 @@ public class HardeningEnforcer { public static final int METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS = 300; public HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, - PackageManager pm) { + PackageManager pm, EventLogger logger) { mContext = ctxt; mIsAutomotive = isAutomotive; mAppOps = appOps; mActivityManager = ctxt.getSystemService(ActivityManager.class); mPackageManager = pm; - } - - protected void dump(PrintWriter pw) { - // log - mEventLogger.dump(pw); + mEventLogger = logger; } /** diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 1c01fb9f19e0..e2e06b63c7d6 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -1745,7 +1745,6 @@ public final class PlaybackActivityMonitor eventValues[0] = eventValue; sEventLogger.enqueue( new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValues)); - final AudioPlaybackConfiguration apc = mPlayers.get(piid); if (apc == null || !apc.handleMutedEvent(eventValue)) { break; // do not dispatch diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index c19d2c9091c3..21c9b876ab46 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -16,6 +16,9 @@ package com.android.server.display; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -1181,6 +1184,14 @@ public class BrightnessTracker { } public RootTaskInfo getFocusedStack() throws RemoteException { + if (UserManager.isVisibleBackgroundUsersEnabled()) { + // In MUMD (Multiple Users on Multiple Displays) system, the top most focused stack + // could be on the secondary display with a user signed on its display so get the + // root task info only on the default display. + return ActivityTaskManager.getService().getRootTaskInfoOnDisplay( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED, + Display.DEFAULT_DISPLAY); + } return ActivityTaskManager.getService().getFocusedRootTaskInfo(); } diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 93d9b8d30a2e..25a2f60b85b2 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -643,8 +643,9 @@ final class ColorFade { .setSecure(isSecure) .setBLASTLayer(); mBLASTSurfaceControl = b.build(); - mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", mBLASTSurfaceControl, - mDisplayWidth, mDisplayHeight, PixelFormat.TRANSLUCENT); + mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", /*updateDestinationFrame*/ true); + mBLASTBufferQueue.update(mBLASTSurfaceControl, mDisplayWidth, mDisplayHeight, + PixelFormat.TRANSLUCENT); mSurface = mBLASTBufferQueue.createSurface(); } return true; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ca001b9c7e6d..a37e9c3ac1b8 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -4382,26 +4382,29 @@ public final class DisplayManagerService extends SystemService { // would be unusual to do so. The method returns true on success. // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. public boolean dispatchPending() { + Event[] pending; + synchronized (mCallback) { + if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) { + return true; + } + if (!isReadyLocked()) { + return false; + } + pending = new Event[mPendingEvents.size()]; + pending = mPendingEvents.toArray(pending); + mPendingEvents.clear(); + } try { - synchronized (mCallback) { - if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) { - return true; - } - if (!isReadyLocked()) { - return false; - } - for (int i = 0; i < mPendingEvents.size(); i++) { - Event displayEvent = mPendingEvents.get(i); - if (DEBUG) { - Slog.d(TAG, "Send pending display event #" + i + " " - + displayEvent.displayId + "/" - + displayEvent.event + " to " + mUid + "/" + mPid); - } - transmitDisplayEvent(displayEvent.displayId, displayEvent.event); + for (int i = 0; i < pending.length; i++) { + Event displayEvent = pending[i]; + if (DEBUG) { + Slog.d(TAG, "Send pending display event #" + i + " " + + displayEvent.displayId + "/" + + displayEvent.event + " to " + mUid + "/" + mPid); } - mPendingEvents.clear(); - return true; + transmitDisplayEvent(displayEvent.displayId, displayEvent.event); } + return true; } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify process " + mPid + " that display topology changed, assuming it died.", ex); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index d37dd3018fde..b49c01b3e2a8 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -83,6 +83,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular"; + private static final double DEFAULT_DISPLAY_SIZE = 24.0; + private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>(); private final Injector mInjector; @@ -526,6 +528,21 @@ final class LocalDisplayAdapter extends DisplayAdapter { private int getLogicalDensity() { DensityMapping densityMapping = getDisplayDeviceConfig().getDensityMapping(); if (densityMapping == null) { + if (getFeatureFlags().isBaseDensityForExternalDisplaysEnabled() + && !mStaticDisplayInfo.isInternal) { + double dpi; + + if (mActiveSfDisplayMode.xDpi > 0 && mActiveSfDisplayMode.yDpi > 0) { + dpi = Math.sqrt((Math.pow(mActiveSfDisplayMode.xDpi, 2) + + Math.pow(mActiveSfDisplayMode.yDpi, 2)) / 2); + } else { + // xDPI and yDPI is missing, calculate DPI from display resolution and + // default display size + dpi = Math.sqrt(Math.pow(mInfo.width, 2) + Math.pow(mInfo.height, 2)) + / DEFAULT_DISPLAY_SIZE; + } + return (int) (dpi + 0.5); + } return (int) (mStaticDisplayInfo.density * 160 + 0.5); } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 0c04be10d06d..67b1ec305d7f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.service.dreams.Flags.allowDreamWhenPostured; import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall; import static android.service.dreams.Flags.dreamHandlesBeingObscured; @@ -110,12 +111,13 @@ public final class DreamManagerService extends SystemService { /** Constants for the when to activate dreams. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_DOCK_OR_CHARGE}) + @IntDef({DREAM_DISABLED, DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_POSTURED}) public @interface WhenToDream {} - private static final int DREAM_DISABLED = 0x0; - private static final int DREAM_ON_DOCK = 0x1; - private static final int DREAM_ON_CHARGE = 0x2; - private static final int DREAM_ON_DOCK_OR_CHARGE = 0x3; + + private static final int DREAM_DISABLED = 0; + private static final int DREAM_ON_DOCK = 1 << 0; + private static final int DREAM_ON_CHARGE = 1 << 1; + private static final int DREAM_ON_POSTURED = 1 << 2; private final Object mLock = new Object(); @@ -137,6 +139,7 @@ public final class DreamManagerService extends SystemService { private final boolean mDreamsEnabledByDefaultConfig; private final boolean mDreamsActivatedOnChargeByDefault; private final boolean mDreamsActivatedOnDockByDefault; + private final boolean mDreamsActivatedOnPosturedByDefault; private final boolean mKeepDreamingWhenUnpluggingDefault; private final boolean mDreamsDisabledByAmbientModeSuppressionConfig; @@ -152,6 +155,7 @@ public final class DreamManagerService extends SystemService { @WhenToDream private int mWhenToDream; private boolean mIsDocked; private boolean mIsCharging; + private boolean mIsPostured; // A temporary dream component that, when present, takes precedence over user configured dream // component. @@ -270,6 +274,8 @@ public final class DreamManagerService extends SystemService { com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamsActivatedOnPosturedByDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault); mSettingsObserver = new SettingsObserver(mHandler); mKeepDreamingWhenUnpluggingDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_keepDreamingWhenUnplugging); @@ -328,6 +334,9 @@ public final class DreamManagerService extends SystemService { Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK), false, mSettingsObserver, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED), + false, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SCREENSAVER_ENABLED), false, mSettingsObserver, UserHandle.USER_ALL); @@ -392,6 +401,8 @@ public final class DreamManagerService extends SystemService { pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting); pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault); pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault); + pw.println("mDreamsActivatedOnPosturedByDefault=" + + mDreamsActivatedOnPosturedByDefault); pw.println("mIsDocked=" + mIsDocked); pw.println("mIsCharging=" + mIsCharging); pw.println("mWhenToDream=" + mWhenToDream); @@ -409,15 +420,28 @@ public final class DreamManagerService extends SystemService { synchronized (mLock) { final ContentResolver resolver = mContext.getContentResolver(); - final int activateWhenCharging = (Settings.Secure.getIntForUser(resolver, + mWhenToDream = DREAM_DISABLED; + + if ((Settings.Secure.getIntForUser(resolver, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, mDreamsActivatedOnChargeByDefault ? 1 : 0, - UserHandle.USER_CURRENT) != 0) ? DREAM_ON_CHARGE : DREAM_DISABLED; - final int activateWhenDocked = (Settings.Secure.getIntForUser(resolver, + UserHandle.USER_CURRENT) != 0)) { + mWhenToDream |= DREAM_ON_CHARGE; + } + + if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, mDreamsActivatedOnDockByDefault ? 1 : 0, - UserHandle.USER_CURRENT) != 0) ? DREAM_ON_DOCK : DREAM_DISABLED; - mWhenToDream = activateWhenCharging + activateWhenDocked; + UserHandle.USER_CURRENT) != 0) { + mWhenToDream |= DREAM_ON_DOCK; + } + + if (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + mDreamsActivatedOnPosturedByDefault ? 1 : 0, + UserHandle.USER_CURRENT) != 0) { + mWhenToDream |= DREAM_ON_POSTURED; + } mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver, Settings.Secure.SCREENSAVER_ENABLED, @@ -508,6 +532,10 @@ public final class DreamManagerService extends SystemService { return mIsDocked; } + if ((mWhenToDream & DREAM_ON_POSTURED) == DREAM_ON_POSTURED) { + return mIsPostured; + } + return false; } } @@ -646,6 +674,14 @@ public final class DreamManagerService extends SystemService { } } + private void setDevicePosturedInternal(boolean isPostured) { + Slog.d(TAG, "Device postured: " + isPostured); + synchronized (mLock) { + mIsPostured = isPostured; + mHandler.post(() -> mPowerManagerInternal.setDevicePostured(isPostured)); + } + } + /** * If doze is true, returns the doze component for the user. * Otherwise, returns the system dream component, if present. @@ -1294,6 +1330,22 @@ public final class DreamManagerService extends SystemService { } } + @Override + public void setDevicePostured(boolean isPostured) { + if (!allowDreamWhenPostured()) { + return; + } + + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + setDevicePosturedInternal(isPostured); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, int callingUid) { if (dreamPackageName == null || packageName == null) { diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java index ff1a74af02e2..9118c46e3b22 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java @@ -61,6 +61,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { @VisibleForTesting static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; + // State in which we wait for device to complete a possible power state change triggered by + // <Set Stream Path>. + @VisibleForTesting + static final int STATE_WAIT_FOR_POWER_STATE_CHANGE = 4; + private final HdmiDeviceInfo mTarget; private final HdmiCecMessage mGivePowerStatus; private final boolean mIsCec20; @@ -100,7 +105,12 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { // Wake-up on <Set Stream Path> was not mandatory before CEC 2.0. // The message is re-sent at the end of the action for devices that don't support 2.0. sendSetStreamPath(); + mState = STATE_WAIT_FOR_POWER_STATE_CHANGE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + return true; + } + private void checkForPowerStateChange() { if (!mIsCec20) { queryDevicePowerStatus(); } else { @@ -114,12 +124,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { queryDevicePowerStatus(); } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { finishWithCallback(HdmiControlManager.RESULT_SUCCESS); - return true; + return; } } mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); - return true; } private void queryDevicePowerStatus() { @@ -210,6 +219,9 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); break; + case STATE_WAIT_FOR_POWER_STATE_CHANGE: + checkForPowerStateChange(); + break; } } diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 93fdbc787ed0..fd755e3cefe2 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -87,7 +87,20 @@ final class InputGestureManager { createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON), createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON), createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON), - createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON) + createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON), + // Used for magnification viewport control. + createKeyTrigger(KeyEvent.KEYCODE_MINUS, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_EQUALS, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON), + createKeyTrigger(KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON) )); public InputGestureManager(Context context) { @@ -216,24 +229,6 @@ final class InputGestureManager { systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP)); - systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN)); systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION)); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 45c7cffd462b..7b81fc92e83d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2189,7 +2189,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mVdmInternal == null) { mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); } - if (mVdmInternal == null || !android.companion.virtual.flags.Flags.vdmCustomIme()) { + if (mVdmInternal == null) { return currentMethodId; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 2615a76ac279..2b0ca145372b 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -334,7 +334,7 @@ public class ContextHubService extends IContextHubService.Stub { if (Flags.offloadApi() && Flags.offloadImplementation()) { HubInfoRegistry registry; try { - registry = new HubInfoRegistry(mContextHubWrapper); + registry = new HubInfoRegistry(mContext, mContextHubWrapper); mEndpointManager = new ContextHubEndpointManager( mContext, mContextHubWrapper, registry, mTransactionManager); @@ -821,6 +821,13 @@ public class ContextHubService extends IContextHubService.Stub { mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback); } + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @Override + public void onDiscoveryCallbackFinished() throws RemoteException { + super.onDiscoveryCallbackFinished_enforcePermission(); + mHubInfoRegistry.onDiscoveryCallbackFinished(); + } + private void checkEndpointDiscoveryPreconditions() { if (mHubInfoRegistry == null) { Log.e(TAG, "Hub endpoint registry failed to initialize"); diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java index bf54fd720d42..711383bbca37 100644 --- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java +++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java @@ -16,12 +16,18 @@ package com.android.server.location.contexthub; +import android.content.Context; import android.hardware.contexthub.HubEndpointInfo; import android.hardware.contexthub.HubServiceInfo; import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback; import android.hardware.location.HubInfo; -import android.os.DeadObjectException; +import android.os.Binder; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.Process; import android.os.RemoteException; +import android.os.WorkSource; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; @@ -34,10 +40,15 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback { private static final String TAG = "HubInfoRegistry"; + + /** The duration of wakelocks acquired during discovery callbacks */ + private static final long WAKELOCK_TIMEOUT_MILLIS = 5 * 1000; + private final Object mLock = new Object(); private final IContextHubWrapper mContextHubWrapper; @@ -53,21 +64,37 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl * A wrapper class that is used to store arguments to * ContextHubManager.registerEndpointCallback. */ - private static class DiscoveryCallback { + private static class DiscoveryCallback implements IBinder.DeathRecipient { + private final HubInfoRegistry mHubInfoRegistry; private final IContextHubEndpointDiscoveryCallback mCallback; private final Optional<Long> mEndpointId; private final Optional<String> mServiceDescriptor; - DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) { + // True if the binder death recipient fired + private final AtomicBoolean mBinderDied = new AtomicBoolean(false); + + DiscoveryCallback( + HubInfoRegistry registry, + IContextHubEndpointDiscoveryCallback callback, + long endpointId) + throws RemoteException { + mHubInfoRegistry = registry; mCallback = callback; mEndpointId = Optional.of(endpointId); mServiceDescriptor = Optional.empty(); + attachDeathRecipient(); } - DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) { + DiscoveryCallback( + HubInfoRegistry registry, + IContextHubEndpointDiscoveryCallback callback, + String serviceDescriptor) + throws RemoteException { + mHubInfoRegistry = registry; mCallback = callback; mEndpointId = Optional.empty(); mServiceDescriptor = Optional.of(serviceDescriptor); + attachDeathRecipient(); } public IContextHubEndpointDiscoveryCallback getCallback() { @@ -79,6 +106,10 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl * @return true if info matches */ public boolean isMatch(HubEndpointInfo info) { + if (mBinderDied.get()) { + Log.w(TAG, "Callback died, isMatch returning false"); + return false; + } if (mEndpointId.isPresent()) { return mEndpointId.get() == info.getIdentifier().getEndpoint(); } @@ -91,6 +122,17 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl } return false; } + + @Override + public void binderDied() { + Log.d(TAG, "Binder died for discovery callback"); + mBinderDied.set(true); + mHubInfoRegistry.unregisterEndpointDiscoveryCallback(mCallback); + } + + private void attachDeathRecipient() throws RemoteException { + mCallback.asBinder().linkToDeath(this, 0 /* flags */); + } } /* The list of discovery callbacks registered with the service */ @@ -99,7 +141,11 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl private final Object mCallbackLock = new Object(); - HubInfoRegistry(IContextHubWrapper contextHubWrapper) throws InstantiationException { + /** Wakelock held while endpoint callbacks are being invoked */ + private final WakeLock mWakeLock; + + HubInfoRegistry(Context context, IContextHubWrapper contextHubWrapper) + throws InstantiationException { mContextHubWrapper = contextHubWrapper; try { refreshCachedHubs(); @@ -109,6 +155,16 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl Log.e(TAG, error, e); throw new InstantiationException(error); } + + PowerManager powerManager = context.getSystemService(PowerManager.class); + if (powerManager == null) { + String error = "PowerManager was null"; + Log.e(TAG, error); + throw new InstantiationError(error); + } + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mWakeLock.setWorkSource(new WorkSource(Process.myUid(), context.getPackageName())); + mWakeLock.setReferenceCounted(true); } /** Retrieve the list of hubs available. */ @@ -178,12 +234,7 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl try { cb.onEndpointsStarted(infoList); } catch (RemoteException e) { - if (e instanceof DeadObjectException) { - Log.w(TAG, "onEndpointStarted: callback died, unregistering"); - unregisterEndpointDiscoveryCallback(cb); - } else { - Log.e(TAG, "Exception while calling onEndpointsStarted", e); - } + Log.e(TAG, "Exception while calling onEndpointsStarted", e); } }); } @@ -208,12 +259,7 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl cb.onEndpointsStopped( infoList, ContextHubServiceUtil.toAppHubEndpointReason(reason)); } catch (RemoteException e) { - if (e instanceof DeadObjectException) { - Log.w(TAG, "onEndpointStopped: callback died, unregistering"); - unregisterEndpointDiscoveryCallback(cb); - } else { - Log.e(TAG, "Exception while calling onEndpointsStopped", e); - } + Log.e(TAG, "Exception while calling onEndpointsStopped", e); } }); } @@ -254,7 +300,11 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl Objects.requireNonNull(callback, "callback cannot be null"); synchronized (mCallbackLock) { checkCallbackAlreadyRegistered(callback); - mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId)); + try { + mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(this, callback, endpointId)); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while adding discovery callback", e); + } } } @@ -264,7 +314,12 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl Objects.requireNonNull(callback, "callback cannot be null"); synchronized (mCallbackLock) { checkCallbackAlreadyRegistered(callback); - mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor)); + try { + mEndpointDiscoveryCallbacks.add( + new DiscoveryCallback(this, callback, serviceDescriptor)); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while adding discovery callback", e); + } } } @@ -282,6 +337,11 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl } } + /* package */ + void onDiscoveryCallbackFinished() { + releaseWakeLock(); + } + private void checkCallbackAlreadyRegistered( IContextHubEndpointDiscoveryCallback callback) { synchronized (mCallbackLock) { @@ -315,6 +375,7 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl } } + acquireWakeLock(); consumer.accept( discoveryCallback.getCallback(), infoList.toArray(new HubEndpointInfo[infoList.size()])); @@ -322,6 +383,26 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl } } + private void acquireWakeLock() { + Binder.withCleanCallingIdentity( + () -> { + mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS); + }); + } + + private void releaseWakeLock() { + Binder.withCleanCallingIdentity( + () -> { + if (mWakeLock.isHeld()) { + try { + mWakeLock.release(); + } catch (RuntimeException e) { + Log.e(TAG, "Releasing the wakelock fails - ", e); + } + } + }); + } + void dump(IndentingPrintWriter ipw) { synchronized (mLock) { dumpLocked(ipw); diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index efc1b9959c0f..6ad7ea7b768f 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -16,18 +16,34 @@ package com.android.server.media.quality; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_ENABLED; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_DISABLED; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_METADATA_AVAILABLE; +import static android.media.quality.AmbientBacklightEvent.AMBIENT_BACKLIGHT_EVENT_INTERRUPTED; + +import android.annotation.NonNull; import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; import android.hardware.tv.mediaquality.IMediaQuality; +import android.hardware.tv.mediaquality.PictureParameter; +import android.hardware.tv.mediaquality.PictureParameters; +import android.hardware.tv.mediaquality.SoundParameter; +import android.hardware.tv.mediaquality.SoundParameters; +import android.media.quality.AmbientBacklightEvent; +import android.media.quality.AmbientBacklightMetadata; import android.media.quality.AmbientBacklightSettings; import android.media.quality.IAmbientBacklightCallback; import android.media.quality.IMediaQualityManager; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; import android.media.quality.MediaQualityContract.BaseParameters; +import android.media.quality.MediaQualityContract.PictureQuality; +import android.media.quality.MediaQualityContract.SoundQuality; import android.media.quality.MediaQualityManager; import android.media.quality.ParameterCapability; import android.media.quality.PictureProfile; @@ -36,12 +52,14 @@ import android.media.quality.SoundProfile; import android.media.quality.SoundProfileHandle; import android.os.Binder; import android.os.Bundle; +import android.os.Environment; import android.os.IBinder; import android.os.PersistableBundle; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -53,6 +71,7 @@ import com.android.server.utils.Slogf; import org.json.JSONException; import org.json.JSONObject; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -60,6 +79,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.UUID; import java.util.stream.Collectors; @@ -71,24 +91,46 @@ public class MediaQualityService extends SystemService { private static final boolean DEBUG = false; private static final String TAG = "MediaQualityService"; + private static final String ALLOWLIST = "allowlist"; + private static final String PICTURE_PROFILE_PREFERENCE = "picture_profile_preference"; + private static final String SOUND_PROFILE_PREFERENCE = "sound_profile_preference"; + private static final String COMMA_DELIMITER = ","; private static final int MAX_UUID_GENERATION_ATTEMPTS = 10; private final Context mContext; private final MediaQualityDbHelper mMediaQualityDbHelper; private final BiMap<Long, String> mPictureProfileTempIdMap; private final BiMap<Long, String> mSoundProfileTempIdMap; + private IMediaQuality mMediaQuality; + private final HalAmbientBacklightCallback mHalAmbientBacklightCallback; + private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>(); private final PackageManager mPackageManager; private final SparseArray<UserState> mUserStates = new SparseArray<>(); - private IMediaQuality mMediaQuality; + private SharedPreferences mPictureProfileSharedPreference; + private SharedPreferences mSoundProfileSharedPreference; public MediaQualityService(Context context) { super(context); mContext = context; + mHalAmbientBacklightCallback = new HalAmbientBacklightCallback(); mPackageManager = mContext.getPackageManager(); mPictureProfileTempIdMap = new BiMap<>(); mSoundProfileTempIdMap = new BiMap<>(); mMediaQualityDbHelper = new MediaQualityDbHelper(mContext); mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true); mMediaQualityDbHelper.setIdleConnectionTimeout(30); + + // The package info in the context isn't initialized in the way it is for normal apps, + // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we + // build the path manually below using the same policy that appears in ContextImpl. + final Context deviceContext = mContext.createDeviceProtectedStorageContext(); + final File pictureProfilePrefs = new File(Environment.getDataSystemDirectory(), + PICTURE_PROFILE_PREFERENCE); + mPictureProfileSharedPreference = deviceContext.getSharedPreferences( + pictureProfilePrefs, Context.MODE_PRIVATE); + final File soundProfilePrefs = new File(Environment.getDataSystemDirectory(), + SOUND_PROFILE_PREFERENCE); + mSoundProfileSharedPreference = deviceContext.getSharedPreferences( + soundProfilePrefs, Context.MODE_PRIVATE); } @Override @@ -97,6 +139,13 @@ public class MediaQualityService extends SystemService { if (binder != null) { Slogf.d(TAG, "binder is not null"); mMediaQuality = IMediaQuality.Stub.asInterface(binder); + if (mMediaQuality != null) { + try { + mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight detector callback", e); + } + } } publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService()); @@ -282,10 +331,208 @@ public class MediaQualityService extends SystemService { notifyOnPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } - // TODO: pass the profile ID to MediaQuality HAL when ready. + + PictureProfile pictureProfile = getPictureProfile( + mPictureProfileTempIdMap.getKey(profileId)); + PersistableBundle params = pictureProfile.getParameters(); + + try { + if (mMediaQuality != null) { + PictureParameter[] pictureParameters = + convertPersistableBundleToPictureParameterList(params); + + PictureParameters pp = new PictureParameters(); + pp.pictureParameters = pictureParameters; + + mMediaQuality.sendDefaultPictureParameters(pp); + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set default picture profile", e); + } return false; } + private PictureParameter[] convertPersistableBundleToPictureParameterList( + PersistableBundle params) { + List<PictureParameter> pictureParams = new ArrayList<>(); + if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) { + pictureParams.add(PictureParameter.brightness(params.getLong( + PictureQuality.PARAMETER_BRIGHTNESS))); + } + if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) { + pictureParams.add(PictureParameter.contrast(params.getInt( + PictureQuality.PARAMETER_CONTRAST))); + } + if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) { + pictureParams.add(PictureParameter.sharpness(params.getInt( + PictureQuality.PARAMETER_SHARPNESS))); + } + if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) { + pictureParams.add(PictureParameter.saturation(params.getInt( + PictureQuality.PARAMETER_SATURATION))); + } + if (params.containsKey(PictureQuality.PARAMETER_HUE)) { + pictureParams.add(PictureParameter.hue(params.getInt( + PictureQuality.PARAMETER_HUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) { + pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) { + pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) { + pictureParams.add(PictureParameter.colorTunerHue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) { + pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) { + pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) { + pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) { + pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) { + pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) { + pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) { + pictureParams.add(PictureParameter.noiseReduction( + (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION))); + } + if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) { + pictureParams.add(PictureParameter.mpegNoiseReduction( + (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION))); + } + if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) { + pictureParams.add(PictureParameter.fleshTone( + (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE))); + } + if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) { + pictureParams.add(PictureParameter.deContour( + (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR))); + } + if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) { + pictureParams.add(PictureParameter.dynamicLumaControl( + (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL))); + } + if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) { + pictureParams.add(PictureParameter.filmMode(params.getBoolean( + PictureQuality.PARAMETER_FILM_MODE))); + } + if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) { + pictureParams.add(PictureParameter.blueStretch(params.getBoolean( + PictureQuality.PARAMETER_BLUE_STRETCH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) { + pictureParams.add(PictureParameter.colorTune(params.getBoolean( + PictureQuality.PARAMETER_COLOR_TUNE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) { + pictureParams.add(PictureParameter.colorTemperature( + (byte) params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE))); + } + if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) { + pictureParams.add(PictureParameter.globeDimming(params.getBoolean( + PictureQuality.PARAMETER_GLOBAL_DIMMING))); + } + if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) { + pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean( + PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED))); + } + if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) { + pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean( + PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) { + pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) { + pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) { + pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN))); + } + + /** + * TODO: add conversion for following after adding to MediaQualityContract + * + * PictureParameter.levelRange + * PictureParameter.gamutMapping + * PictureParameter.pcMode + * PictureParameter.lowLatency + * PictureParameter.vrr + * PictureParameter.cvrr + * PictureParameter.hdmiRgbRange + * PictureParameter.colorSpace + * PictureParameter.panelInitMaxLuminceNits + * PictureParameter.panelInitMaxLuminceValid + * PictureParameter.gamma + * PictureParameter.colorTemperatureRedOffset + * PictureParameter.colorTemperatureGreenOffset + * PictureParameter.colorTemperatureBlueOffset + * PictureParameter.elevenPointRed + * PictureParameter.elevenPointGreen + * PictureParameter.elevenPointBlue + * PictureParameter.lowBlueLight + * PictureParameter.LdMode + * PictureParameter.osdRedGain + * PictureParameter.osdGreenGain + * PictureParameter.osdBlueGain + * PictureParameter.osdRedOffset + * PictureParameter.osdGreenOffset + * PictureParameter.osdBlueOffset + * PictureParameter.osdHue + * PictureParameter.osdSaturation + * PictureParameter.osdContrast + * PictureParameter.colorTunerSwitch + * PictureParameter.colorTunerHueRed + * PictureParameter.colorTunerHueGreen + * PictureParameter.colorTunerHueBlue + * PictureParameter.colorTunerHueCyan + * PictureParameter.colorTunerHueMagenta + * PictureParameter.colorTunerHueYellow + * PictureParameter.colorTunerHueFlesh + * PictureParameter.colorTunerSaturationRed + * PictureParameter.colorTunerSaturationGreen + * PictureParameter.colorTunerSaturationBlue + * PictureParameter.colorTunerSaturationCyan + * PictureParameter.colorTunerSaturationMagenta + * PictureParameter.colorTunerSaturationYellow + * PictureParameter.colorTunerSaturationFlesh + * PictureParameter.colorTunerLuminanceRed + * PictureParameter.colorTunerLuminanceGreen + * PictureParameter.colorTunerLuminanceBlue + * PictureParameter.colorTunerLuminanceCyan + * PictureParameter.colorTunerLuminanceMagenta + * PictureParameter.colorTunerLuminanceYellow + * PictureParameter.colorTunerLuminanceFlesh + * PictureParameter.activeProfile + * PictureParameter.pictureQualityEventType + */ + return (PictureParameter[]) pictureParams.toArray(); + } + @Override public List<String> getPictureProfilePackageNames(UserHandle user) { if (!hasGlobalPictureQualityServicePermission()) { @@ -503,10 +750,77 @@ public class MediaQualityService extends SystemService { notifyOnSoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } - // TODO: pass the profile ID to MediaQuality HAL when ready. + + SoundProfile soundProfile = getSoundProfile(mSoundProfileTempIdMap.getKey(profileId)); + PersistableBundle params = soundProfile.getParameters(); + + try { + if (mMediaQuality != null) { + SoundParameter[] soundParameters = + convertPersistableBundleToSoundParameterList(params); + + SoundParameters sp = new SoundParameters(); + sp.soundParameters = soundParameters; + + mMediaQuality.sendDefaultSoundParameters(sp); + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set default sound profile", e); + } return false; } + private SoundParameter[] convertPersistableBundleToSoundParameterList( + PersistableBundle params) { + List<SoundParameter> soundParams = new ArrayList<>(); + if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) { + soundParams.add(SoundParameter.balance(params.getInt( + SoundQuality.PARAMETER_BALANCE))); + } + if (params.containsKey(SoundQuality.PARAMETER_BASS)) { + soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS))); + } + if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) { + soundParams.add(SoundParameter.treble(params.getInt( + SoundQuality.PARAMETER_TREBLE))); + } + if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) { + soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean( + SoundQuality.PARAMETER_SURROUND_SOUND))); + } + if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) { + soundParams.add(SoundParameter.speakersEnabled(params.getBoolean( + SoundQuality.PARAMETER_SPEAKERS))); + } + if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) { + soundParams.add(SoundParameter.speakersDelayMs(params.getInt( + SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS))); + } + if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) { + soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean( + SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL))); + } + if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) { + soundParams.add(SoundParameter.dtsDrc(params.getBoolean( + SoundQuality.PARAMETER_DTS_DRC))); + } + if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) { + soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean( + SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS))); + } + //TODO: equalizerDetail + //TODO: downmixMode + //TODO: enhancedAudioReturnChannelEnabled + //TODO: dolbyAudioProcessing + //TODO: dolbyDialogueEnhancer + //TODO: dtsVirtualX + //TODO: digitalOutput + //TODO: activeProfile + //TODO: soundStyle + return (SoundParameter[]) soundParams.toArray(); + } + @Override public List<String> getSoundProfilePackageNames(UserHandle user) { if (!hasGlobalSoundQualityServicePermission()) { @@ -905,24 +1219,86 @@ public class MediaQualityService extends SystemService { @Override public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) { + if (DEBUG) { + Slogf.d(TAG, "registerAmbientBacklightCallback"); + } + if (!hasReadColorZonesPermission()) { //TODO: error handling } + + String callingPackageName = getPackageOfCallingUid(); + + synchronized (mCallbackRecords) { + AmbientBacklightCallbackRecord record = mCallbackRecords.get(callingPackageName); + if (record != null) { + if (record.mCallback.asBinder().equals(callback.asBinder())) { + Slog.w(TAG, "AmbientBacklight Callback already registered"); + return; + } + record.release(); + mCallbackRecords.remove(callingPackageName); + } + mCallbackRecords.put(callingPackageName, + new AmbientBacklightCallbackRecord(callingPackageName, callback)); + } } @Override public void setAmbientBacklightSettings( AmbientBacklightSettings settings, UserHandle user) { + if (DEBUG) { + Slogf.d(TAG, "setAmbientBacklightSettings " + settings); + } + if (!hasReadColorZonesPermission()) { //TODO: error handling } + + try { + if (mMediaQuality != null) { + android.hardware.tv.mediaquality.AmbientBacklightSettings halSettings = + new android.hardware.tv.mediaquality.AmbientBacklightSettings(); + halSettings.uid = Binder.getCallingUid(); + halSettings.source = (byte) settings.getSource(); + halSettings.maxFramerate = settings.getMaxFps(); + halSettings.colorFormat = (byte) settings.getColorFormat(); + halSettings.hZonesNumber = settings.getHorizontalZonesCount(); + halSettings.vZonesNumber = settings.getVerticalZonesCount(); + halSettings.hasLetterbox = settings.isLetterboxOmitted(); + halSettings.colorThreshold = settings.getThreshold(); + + mMediaQuality.setAmbientBacklightDetector(halSettings); + + mHalAmbientBacklightCallback.setAmbientBacklightClientPackageName( + getPackageOfCallingUid()); + + if (DEBUG) { + Slogf.d(TAG, "set ambient settings package: " + halSettings.uid); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight settings", e); + } } @Override public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) { + if (DEBUG) { + Slogf.d(TAG, "setAmbientBacklightEnabled " + enabled); + } if (!hasReadColorZonesPermission()) { //TODO: error handling } + try { + if (mMediaQuality != null) { + mMediaQuality.setAmbientBacklightDetectionEnabled(enabled); + } + } catch (UnsupportedOperationException e) { + Slog.e(TAG, "The current device is not supported"); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight enabled", e); + } } @Override @@ -937,6 +1313,11 @@ public class MediaQualityService extends SystemService { notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } + String allowlist = mPictureProfileSharedPreference.getString(ALLOWLIST, null); + if (allowlist != null) { + String[] stringArray = allowlist.split(COMMA_DELIMITER); + return new ArrayList<>(Arrays.asList(stringArray)); + } return new ArrayList<>(); } @@ -946,6 +1327,9 @@ public class MediaQualityService extends SystemService { notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } + SharedPreferences.Editor editor = mPictureProfileSharedPreference.edit(); + editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages)); + editor.commit(); } @Override @@ -954,6 +1338,11 @@ public class MediaQualityService extends SystemService { notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } + String allowlist = mSoundProfileSharedPreference.getString(ALLOWLIST, null); + if (allowlist != null) { + String[] stringArray = allowlist.split(COMMA_DELIMITER); + return new ArrayList<>(Arrays.asList(stringArray)); + } return new ArrayList<>(); } @@ -963,6 +1352,9 @@ public class MediaQualityService extends SystemService { notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } + SharedPreferences.Editor editor = mSoundProfileSharedPreference.edit(); + editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages)); + editor.commit(); } @Override @@ -979,10 +1371,10 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { - mMediaQuality.setAutoPqEnabled(enabled); + if (mMediaQuality.isAutoPqSupported()) { + mMediaQuality.setAutoPqEnabled(enabled); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { Slog.e(TAG, "Failed to set auto picture quality", e); } @@ -992,10 +1384,10 @@ public class MediaQualityService extends SystemService { public boolean isAutoPictureQualityEnabled(UserHandle user) { try { if (mMediaQuality != null) { - return mMediaQuality.getAutoPqEnabled(); + if (mMediaQuality.isAutoPqSupported()) { + return mMediaQuality.getAutoPqEnabled(); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { Slog.e(TAG, "Failed to get auto picture quality", e); } @@ -1011,12 +1403,12 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { - mMediaQuality.setAutoSrEnabled(enabled); + if (mMediaQuality.isAutoSrSupported()) { + mMediaQuality.setAutoSrEnabled(enabled); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to set auto super resolution", e); + Slog.e(TAG, "Failed to set super resolution", e); } } @@ -1024,12 +1416,12 @@ public class MediaQualityService extends SystemService { public boolean isSuperResolutionEnabled(UserHandle user) { try { if (mMediaQuality != null) { - return mMediaQuality.getAutoSrEnabled(); + if (mMediaQuality.isAutoSrSupported()) { + return mMediaQuality.getAutoSrEnabled(); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to get auto super resolution", e); + Slog.e(TAG, "Failed to get super resolution", e); } return false; } @@ -1043,12 +1435,12 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { - mMediaQuality.setAutoAqEnabled(enabled); + if (mMediaQuality.isAutoAqSupported()) { + mMediaQuality.setAutoAqEnabled(enabled); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to set auto audio quality", e); + Slog.e(TAG, "Failed to set auto sound quality", e); } } @@ -1056,12 +1448,12 @@ public class MediaQualityService extends SystemService { public boolean isAutoSoundQualityEnabled(UserHandle user) { try { if (mMediaQuality != null) { - return mMediaQuality.getAutoAqEnabled(); + if (mMediaQuality.isAutoAqSupported()) { + return mMediaQuality.getAutoAqEnabled(); + } } - } catch (UnsupportedOperationException e) { - Slog.e(TAG, "The current device is not supported"); } catch (RemoteException e) { - Slog.e(TAG, "Failed to get auto audio quality", e); + Slog.e(TAG, "Failed to get auto sound quality", e); } return false; } @@ -1119,4 +1511,167 @@ public class MediaQualityService extends SystemService { private UserState getUserStateLocked(int userId) { return mUserStates.get(userId); } + + private final class AmbientBacklightCallbackRecord implements IBinder.DeathRecipient { + final String mPackageName; + final IAmbientBacklightCallback mCallback; + + AmbientBacklightCallbackRecord(@NonNull String pkgName, + @NonNull IAmbientBacklightCallback cb) { + mPackageName = pkgName; + mCallback = cb; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death", e); + } + } + + void release() { + try { + mCallback.asBinder().unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.e(TAG, "Failed to unlink to death", e); + } + } + + @Override + public void binderDied() { + synchronized (mCallbackRecords) { + mCallbackRecords.remove(mPackageName); + } + } + } + + private final class HalAmbientBacklightCallback + extends android.hardware.tv.mediaquality.IMediaQualityCallback.Stub { + private final Object mLock = new Object(); + private String mAmbientBacklightClientPackageName; + + void setAmbientBacklightClientPackageName(@NonNull String packageName) { + synchronized (mLock) { + if (TextUtils.equals(mAmbientBacklightClientPackageName, packageName)) { + return; + } + handleAmbientBacklightInterrupted(); + mAmbientBacklightClientPackageName = packageName; + } + } + + void handleAmbientBacklightInterrupted() { + synchronized (mCallbackRecords) { + if (mAmbientBacklightClientPackageName == null) { + Slog.e(TAG, "Invalid package name in interrupted event"); + return; + } + AmbientBacklightCallbackRecord record = mCallbackRecords.get( + mAmbientBacklightClientPackageName); + if (record == null) { + Slog.e(TAG, "Callback record not found for ambient backlight"); + return; + } + AmbientBacklightEvent event = + new AmbientBacklightEvent( + AMBIENT_BACKLIGHT_EVENT_INTERRUPTED, null); + try { + record.mCallback.onAmbientBacklightEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "Deliver ambient backlight interrupted event failed", e); + } + } + } + + void handleAmbientBacklightEnabled(boolean enabled) { + AmbientBacklightEvent event = + new AmbientBacklightEvent( + enabled ? AMBIENT_BACKLIGHT_EVENT_ENABLED : + AMBIENT_BACKLIGHT_EVENT_DISABLED, null); + synchronized (mCallbackRecords) { + for (AmbientBacklightCallbackRecord record : mCallbackRecords.values()) { + try { + record.mCallback.onAmbientBacklightEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "Deliver ambient backlight enabled event failed", e); + } + } + } + } + + void handleAmbientBacklightMetadataEvent( + @NonNull android.hardware.tv.mediaquality.AmbientBacklightMetadata + halMetadata) { + String halPackageName = mContext.getPackageManager() + .getNameForUid(halMetadata.settings.uid); + if (!TextUtils.equals(mAmbientBacklightClientPackageName, halPackageName)) { + Slog.e(TAG, "Invalid package name in metadata event"); + return; + } + + AmbientBacklightColorFormat[] zonesColorsUnion = halMetadata.zonesColors; + int[] zonesColorsInt = new int[zonesColorsUnion.length]; + + for (int i = 0; i < zonesColorsUnion.length; i++) { + zonesColorsInt[i] = zonesColorsUnion[i].RGB888; + } + + AmbientBacklightMetadata metadata = + new AmbientBacklightMetadata( + halPackageName, + halMetadata.compressAlgorithm, + halMetadata.settings.source, + halMetadata.settings.colorFormat, + halMetadata.settings.hZonesNumber, + halMetadata.settings.vZonesNumber, + zonesColorsInt); + AmbientBacklightEvent event = + new AmbientBacklightEvent( + AMBIENT_BACKLIGHT_EVENT_METADATA_AVAILABLE, metadata); + + synchronized (mCallbackRecords) { + AmbientBacklightCallbackRecord record = mCallbackRecords + .get(halPackageName); + if (record == null) { + Slog.e(TAG, "Callback record not found for ambient backlight metadata"); + return; + } + + try { + record.mCallback.onAmbientBacklightEvent(event); + } catch (RemoteException e) { + Slog.e(TAG, "Deliver ambient backlight metadata event failed", e); + } + } + } + + @Override + public void notifyAmbientBacklightEvent( + android.hardware.tv.mediaquality.AmbientBacklightEvent halEvent) { + synchronized (mLock) { + if (halEvent.getTag() == android.hardware.tv.mediaquality + .AmbientBacklightEvent.Tag.enabled) { + boolean enabled = halEvent.getEnabled(); + if (enabled) { + handleAmbientBacklightEnabled(true); + } else { + handleAmbientBacklightEnabled(false); + } + } else if (halEvent.getTag() == android.hardware.tv.mediaquality + .AmbientBacklightEvent.Tag.metadata) { + handleAmbientBacklightMetadataEvent(halEvent.getMetadata()); + } else { + Slog.e(TAG, "Invalid event type in ambient backlight event"); + } + } + } + + @Override + public synchronized String getInterfaceHash() throws android.os.RemoteException { + return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.HASH; + } + + @Override + public int getInterfaceVersion() throws android.os.RemoteException { + return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.VERSION; + } + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 837003f87598..ef39f1811876 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -762,6 +762,7 @@ public class NotificationManagerService extends SystemService { private int mWarnRemoteViewsSizeBytes; private int mStripRemoteViewsSizeBytes; + private String[] mDefaultUnsupportedAdjustments; @VisibleForTesting protected boolean mShowReviewPermissionsNotification; @@ -2938,6 +2939,9 @@ public class NotificationManagerService extends SystemService { mShowReviewPermissionsNotification = getContext().getResources().getBoolean( R.bool.config_notificationReviewPermissions); + mDefaultUnsupportedAdjustments = getContext().getResources().getStringArray( + R.array.config_notificationDefaultUnsupportedAdjustments); + init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), AppGlobals.getPackageManager(), getContext().getPackageManager(), getLocalService(LightsManager.class), @@ -3033,10 +3037,9 @@ public class NotificationManagerService extends SystemService { switch(atomTag) { case PACKAGE_NOTIFICATION_PREFERENCES: if (notificationClassificationUi()) { - Set<String> pkgs = mAssistants.getPackagesWithKeyTypeAdjustmentSettings(); mPreferencesHelper.pullPackagePreferencesStats(data, getAllUsersNotificationPermissions(), - getPackageSpecificAdjustmentKeyTypes(pkgs)); + new ArrayMap<>()); } else { mPreferencesHelper.pullPackagePreferencesStats(data, getAllUsersNotificationPermissions()); @@ -4397,16 +4400,16 @@ public class NotificationManagerService extends SystemService { @Override @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) { + public @NonNull String[] getTypeAdjustmentDeniedPackages() { checkCallerIsSystemOrSystemUiOrShell(); - return mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg); + return mAssistants.getTypeAdjustmentDeniedPackages(); } + @Override @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type, - boolean enabled) { + public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) { checkCallerIsSystemOrSystemUiOrShell(); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled); + mAssistants.setTypeAdjustmentForPackageState(pkg, enabled); handleSavePolicyFile(); } @@ -7215,7 +7218,7 @@ public class NotificationManagerService extends SystemService { toRemove.add(potentialKey); } else if (notificationClassificationUi() && !mAssistants.isTypeAdjustmentAllowedForPackage( - r.getSbn().getPackageName(), adjustments.getInt(KEY_TYPE))) { + r.getSbn().getPackageName())) { toRemove.add(potentialKey); } } @@ -7552,24 +7555,6 @@ public class NotificationManagerService extends SystemService { return allPermissions; } - @VisibleForTesting - @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected @NonNull Map<String, Set<Integer>> getPackageSpecificAdjustmentKeyTypes( - Set<String> pkgs) { - ArrayMap<String, Set<Integer>> pkgToAllowedTypes = new ArrayMap<>(); - for (String pkg : pkgs) { - int[] allowedTypesArray = mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg); - if (allowedTypesArray != null) { - Set<Integer> allowedTypes = new ArraySet<Integer>(); - for (int i : allowedTypesArray) { - allowedTypes.add(i); - } - pkgToAllowedTypes.append(pkg, allowedTypes); - } - } - return pkgToAllowedTypes; - } - private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { JSONObject dump = new JSONObject(); @@ -11885,11 +11870,7 @@ public class NotificationManagerService extends SystemService { private static final String ATT_DENIED = "denied_adjustments"; private static final String ATT_ENABLED_TYPES = "enabled_key_types"; private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments"; - // Encapsulates a list of packages and the bundle types enabled for each package. - private static final String TAG_TYPES_ENABLED_FOR_APPS = "types_enabled_for_apps"; - // Encapsulates the bundle types enabled for a package. - private static final String ATT_APP_ENABLED_TYPES = "app_enabled_types"; - private static final String ATT_PACKAGE = "package"; + private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps"; private final Object mLock = new Object(); @@ -11905,14 +11886,8 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mLock") private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>(); - // Types of classifications (aka bundles) enabled/allowed for this package. - // If the set is NULL (or package is not in the list), default classification allow list - // (the global one) should be used. - // If the set is empty, that indicates the package explicitly has all classifications - // disallowed. @GuardedBy("mLock") - private Map<String, Set<Integer>> mClassificationTypePackagesEnabledTypes = - new ArrayMap<>(); + private Set<String> mClassificationTypeDeniedPackages = new ArraySet<>(); protected ComponentName mDefaultFromConfig = null; @@ -11990,6 +11965,9 @@ public class NotificationManagerService extends SystemService { } } else { mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES)); + if (mDefaultUnsupportedAdjustments != null) { + mAllowedAdjustments.removeAll(List.of(mDefaultUnsupportedAdjustments)); + } } } @@ -12113,104 +12091,41 @@ public class NotificationManagerService extends SystemService { } } - /** - * Returns whether the type adjustment is allowed for this particular package. - * If no package-specific restrictions have been set, defaults to the same value as - * isAdjustmentKeyTypeAllowed(type). - */ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected boolean isTypeAdjustmentAllowedForPackage(String pkg, - @Adjustment.Types int type) { + protected @NonNull boolean isTypeAdjustmentAllowedForPackage(String pkg) { synchronized (mLock) { if (notificationClassificationUi()) { - if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) { - Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg); - if (enabled != null) { - return enabled.contains(type); - } - } - // If mClassificationTypePackagesEnabledTypes does not contain the pkg, or - // the stored set is null, return the default. - return isAdjustmentKeyTypeAllowed(type); - } - } - return false; - } - - @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected @NonNull Set<String> getPackagesWithKeyTypeAdjustmentSettings() { - if (notificationClassificationUi()) { - Set<String> packagesWithModifications = new ArraySet<String>(); - synchronized (mLock) { - for (String pkg : mClassificationTypePackagesEnabledTypes.keySet()) { - if (mClassificationTypePackagesEnabledTypes.get(pkg) != null) { - packagesWithModifications.add(pkg); - } - } + return !mClassificationTypeDeniedPackages.contains(pkg); } - return packagesWithModifications; } - return new ArraySet<String>(); + return true; } @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - protected @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) { + protected @NonNull String[] getTypeAdjustmentDeniedPackages() { synchronized (mLock) { if (notificationClassificationUi()) { - if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) { - Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg); - if (enabled != null) { - // Convert Set to int[] for return. - int[] returnEnabled = new int[enabled.size()]; - int i = 0; - for (int val: enabled) { - returnEnabled[i] = val; - i++; - } - return returnEnabled; - } - } - // If package is not in the map, or the value is null, return the default. - return getAllowedAdjustmentKeyTypes(); + return mClassificationTypeDeniedPackages.toArray(new String[0]); } } - return new int[]{}; + return new String[]{}; } /** * Set whether a particular package can have its notification channels adjusted to have a * different type by NotificationAssistants. - * Note: once this method is called to enable or disable a specific type for a package, - * the global default is set as the starting point, and the type is enabled/disabled from - * there. Future changes to the global default will not apply automatically to this package. */ @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, - @Adjustment.Types int type, - boolean enabled) { + public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) { if (!notificationClassificationUi()) { return; } synchronized (mLock) { - Set<Integer> enabledTypes = null; - if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) { - enabledTypes = mClassificationTypePackagesEnabledTypes.get(pkg); - } - if (enabledTypes == null) { - // Use global default to start. - enabledTypes = new ArraySet<Integer>(); - // Convert from int[] to Set<Integer> - for (int value : getAllowedAdjustmentKeyTypes()) { - enabledTypes.add(value); - } - } - if (enabled) { - enabledTypes.add(type); + mClassificationTypeDeniedPackages.remove(pkg); } else { - enabledTypes.remove(type); + mClassificationTypeDeniedPackages.add(pkg); } - mClassificationTypePackagesEnabledTypes.put(pkg, enabledTypes); } } @@ -12677,25 +12592,16 @@ public class NotificationManagerService extends SystemService { TextUtils.join(",", mAllowedAdjustmentKeyTypes)); out.endTag(null, ATT_ENABLED_TYPES); if (notificationClassificationUi()) { - out.startTag(null, TAG_TYPES_ENABLED_FOR_APPS); - for (String pkg: mClassificationTypePackagesEnabledTypes.keySet()) { - Set<Integer> allowedTypes = - mClassificationTypePackagesEnabledTypes.get(pkg); - if (allowedTypes != null) { - out.startTag(null, ATT_APP_ENABLED_TYPES); - out.attribute(null, ATT_PACKAGE, pkg); - out.attribute(null, ATT_TYPES, TextUtils.join(",", allowedTypes)); - out.endTag(null, ATT_APP_ENABLED_TYPES); - } - } - out.endTag(null, TAG_TYPES_ENABLED_FOR_APPS); + out.startTag(null, ATT_TYPES_DENIED_APPS); + out.attribute(null, ATT_TYPES, + TextUtils.join(",", mClassificationTypeDeniedPackages)); + out.endTag(null, ATT_TYPES_DENIED_APPS); } } } @Override - protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException, - XmlPullParserException { + protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException { if (!notificationClassification()) { return; } @@ -12722,25 +12628,12 @@ public class NotificationManagerService extends SystemService { } } } - } else if (TAG_TYPES_ENABLED_FOR_APPS.equals(tag)) { - final int appsOuterDepth = parser.getDepth(); + } else if (notificationClassificationUi() && ATT_TYPES_DENIED_APPS.equals(tag)) { + final String apps = XmlUtils.readStringAttribute(parser, ATT_TYPES); synchronized (mLock) { - mClassificationTypePackagesEnabledTypes.clear(); - while (XmlUtils.nextElementWithin(parser, appsOuterDepth)) { - if (!ATT_APP_ENABLED_TYPES.equals(parser.getName())) { - continue; - } - final String app = XmlUtils.readStringAttribute(parser, ATT_PACKAGE); - Set<Integer> allowedTypes = new ArraySet<>(); - final String typesString = XmlUtils.readStringAttribute(parser, ATT_TYPES); - if (!TextUtils.isEmpty(typesString)) { - allowedTypes = Arrays.stream(typesString.split(",")) - .map(Integer::valueOf) - .collect(Collectors.toSet()); - } - // Empty type list is allowed, because empty type list signifies the user - // has manually cleared the package of allowed types. - mClassificationTypePackagesEnabledTypes.put(app, allowedTypes); + mClassificationTypeDeniedPackages.clear(); + if (!TextUtils.isEmpty(apps)) { + mClassificationTypeDeniedPackages.addAll(Arrays.asList(apps.split(","))); } } } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 1aa5ac046ae9..7e853d9d2d0b 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import android.annotation.Nullable; import android.app.NotificationManager; import android.content.ComponentName; import android.media.AudioManager; @@ -26,6 +27,7 @@ import android.provider.Settings.Global; import android.service.notification.IConditionProvider; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigOrigin; import android.service.notification.ZenModeDiff; import android.util.LocalLog; @@ -119,16 +121,17 @@ public class ZenLog { append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e)); } - public static void traceConfig(String reason, ComponentName triggeringComponent, - ZenModeConfig oldConfig, ZenModeConfig newConfig, int callingUid) { + public static void traceConfig(@ConfigOrigin int origin, String reason, + @Nullable ComponentName triggeringComponent, ZenModeConfig oldConfig, + ZenModeConfig newConfig, int callingUid) { ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig); - if (diff == null || !diff.hasDiff()) { - append(TYPE_CONFIG, reason + " no changes"); + if (!diff.hasDiff()) { + append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") no changes"); } else { - append(TYPE_CONFIG, reason - + " - " + triggeringComponent + " : " + callingUid - + ",\n" + (newConfig != null ? newConfig.toString() : null) - + ",\n" + diff); + append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") from uid " + callingUid + + (triggeringComponent != null ? " - " + triggeringComponent : "") + ",\n" + + (newConfig != null ? newConfig.toString() : null) + ",\n" + + diff); } } @@ -241,7 +244,22 @@ public class ZenLog { } } - private static String componentToString(ComponentName component) { + private static String originToString(@ConfigOrigin int origin) { + return switch (origin) { + case ZenModeConfig.ORIGIN_UNKNOWN -> "ORIGIN_UNKNOWN"; + case ZenModeConfig.ORIGIN_INIT -> "ORIGIN_INIT"; + case ZenModeConfig.ORIGIN_INIT_USER -> "ORIGIN_INIT_USER"; + case ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI -> "ORIGIN_USER_IN_SYSTEMUI"; + case ZenModeConfig.ORIGIN_APP -> "ORIGIN_APP"; + case ZenModeConfig.ORIGIN_SYSTEM -> "ORIGIN_SYSTEM"; + case ZenModeConfig.ORIGIN_RESTORE_BACKUP -> "ORIGIN_RESTORE_BACKUP"; + case ZenModeConfig.ORIGIN_USER_IN_APP -> "ORIGIN_USER_IN_APP"; + default -> origin + "??"; + }; + } + + @Nullable + private static String componentToString(@Nullable ComponentName component) { return component != null ? component.toShortString() : null; } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 0a63f3fb36d0..f089c3acdd70 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -659,7 +659,8 @@ public class ZenModeHelper { mContext.getString(R.string.zen_mode_implicit_deactivated), STATE_FALSE); setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), - deactivated, ORIGIN_APP, callingUid); + deactivated, ORIGIN_APP, + "applyGlobalZenModeAsImplicitZenRule: " + callingPkg, callingUid); } } else { // Either create a new rule with a default ZenPolicy, or update an existing rule's @@ -971,26 +972,27 @@ public class ZenModeHelper { if (Flags.modesApi()) { if (rule != null && canManageAutomaticZenRule(rule, callingUid)) { setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), - condition, origin, callingUid); + condition, origin, "setAzrState: " + rule.id, callingUid); } } else { ArrayList<ZenRule> rules = new ArrayList<>(); rules.add(rule); // rule may be null and throw NPE in the next method. - setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid); + setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, + "setAzrState: " + (rule != null ? rule.id : "null!"), callingUid); } } } - void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleDefinition, + void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleConditionId, Condition condition, @ConfigOrigin int origin, int callingUid) { - checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin); + checkSetRuleStateOrigin("setAutomaticZenRuleStateFromConditionProvider", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) return; newConfig = config.copy(); - List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition); + List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleConditionId, condition); if (Flags.modesApi()) { for (int i = matchingRules.size() - 1; i >= 0; i--) { if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) { @@ -998,13 +1000,14 @@ public class ZenModeHelper { } } } - setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, callingUid); + setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, + "setAzrStateFromCps: " + ruleConditionId, callingUid); } } @GuardedBy("mConfigLock") private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, - Condition condition, @ConfigOrigin int origin, int callingUid) { + Condition condition, @ConfigOrigin int origin, String reason, int callingUid) { if (rules == null || rules.isEmpty()) return; if (!Flags.modesUi()) { @@ -1015,7 +1018,7 @@ public class ZenModeHelper { for (ZenRule rule : rules) { applyConditionAndReconsiderOverride(rule, condition, origin); - setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid); + setConfigLocked(config, rule.component, origin, reason, callingUid); } } @@ -2111,13 +2114,14 @@ public class ZenModeHelper { } @GuardedBy("mConfigLock") - private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, - @ConfigOrigin int origin, String reason, int callingUid) { + private boolean setConfigLocked(ZenModeConfig config, + @Nullable ComponentName triggeringComponent, @ConfigOrigin int origin, String reason, + int callingUid) { return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/, callingUid); } - void setConfig(ZenModeConfig config, ComponentName triggeringComponent, + void setConfig(ZenModeConfig config, @Nullable ComponentName triggeringComponent, @ConfigOrigin int origin, String reason, int callingUid) { synchronized (mConfigLock) { setConfigLocked(config, triggeringComponent, origin, reason, callingUid); @@ -2126,7 +2130,7 @@ public class ZenModeHelper { @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, @ConfigOrigin int origin, - String reason, ComponentName triggeringComponent, boolean setRingerMode, + String reason, @Nullable ComponentName triggeringComponent, boolean setRingerMode, int callingUid) { final long identity = Binder.clearCallingIdentity(); try { @@ -2149,7 +2153,7 @@ public class ZenModeHelper { mConfigs.put(config.user, config); } if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable()); - ZenLog.traceConfig(reason, triggeringComponent, mConfig, config, callingUid); + ZenLog.traceConfig(origin, reason, triggeringComponent, mConfig, config, callingUid); // send some broadcasts Policy newPolicy = getNotificationPolicy(config); diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index f23d7823be94..33c122964d77 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -137,16 +137,26 @@ public final class NativeTombstoneManager { return; } - String processName = "UNKNOWN"; final boolean isProtoFile = filename.endsWith(".pb"); - File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb"); - Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile); - if (parsedTombstone.isPresent()) { - processName = parsedTombstone.get().getProcessName(); + // Only process the pb tombstone output, the text version will be generated in + // BootReceiver.filterAndAddTombstoneToDropBox through pbtombstone + if (Flags.protoTombstone() && !isProtoFile) { + return; } - BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock); + File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb"); + + final String processName = handleProtoTombstone(protoPath, isProtoFile) + .map(TombstoneFile::getProcessName) + .orElse("UNKNOWN"); + + if (Flags.protoTombstone()) { + BootReceiver.filterAndAddTombstoneToDropBox(mContext, path, processName, mTmpFileLock); + } else { + BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, + processName, mTmpFileLock); + } // TODO(b/339371242): An optimizer on WearOS is misbehaving and this member is being garbage // collected as it's never referenced inside this class outside of the constructor. But, // it's a file watcher, and needs to stay alive to do its job. So, add a cheap check here to diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig index efdc9b8c164f..5e35cf5f02d3 100644 --- a/services/core/java/com/android/server/os/core_os_flags.aconfig +++ b/services/core/java/com/android/server/os/core_os_flags.aconfig @@ -3,7 +3,7 @@ container: "system" flag { name: "proto_tombstone" - namespace: "proto_tombstone_ns" + namespace: "stability" description: "Use proto tombstones as source of truth for adding to dropbox" bug: "323857385" } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f60e086e7c5d..61429a41370c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4255,8 +4255,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService CarrierAppUtils.disableCarrierAppsUntilPrivileged( mContext.getOpPackageName(), UserHandle.USER_SYSTEM, mContext); - disableSkuSpecificApps(); - // Read the compatibilty setting when the system is ready. boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt( mContext.getContentResolver(), @@ -4390,29 +4388,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - //TODO: b/111402650 - private void disableSkuSpecificApps() { - String[] apkList = mContext.getResources().getStringArray( - R.array.config_disableApksUnlessMatchedSku_apk_list); - String[] skuArray = mContext.getResources().getStringArray( - R.array.config_disableApkUnlessMatchedSku_skus_list); - if (ArrayUtils.isEmpty(apkList)) { - return; - } - String sku = SystemProperties.get("ro.boot.hardware.sku"); - if (!TextUtils.isEmpty(sku) && ArrayUtils.contains(skuArray, sku)) { - return; - } - final Computer snapshot = snapshotComputer(); - for (String packageName : apkList) { - setSystemAppHiddenUntilInstalled(snapshot, packageName, true); - final List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(false); - for (int i = 0; i < users.size(); i++) { - setSystemAppInstallState(snapshot, packageName, false, users.get(i).id); - } - } - } - public PackageFreezer freezePackage(String packageName, int userId, String killReason, int exitInfoReason, InstallRequest request) { return freezePackage(packageName, userId, killReason, exitInfoReason, request, diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index deaa8d8feae1..44d787f790cf 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -356,7 +356,7 @@ public final class PermissionPolicyService extends SystemService { try { manager = new PermissionControllerManager( getUserContext(getContext(), user), PermissionThread.getHandler()); - } catch (IllegalArgumentException exception) { + } catch (IllegalStateException exception) { // There's a possible race condition when a user is being removed Log.e(LOG_TAG, "Could not create PermissionControllerManager for user" + user, exception); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f9e4022f04a0..090707db50a5 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -30,6 +30,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; import static android.os.PowerManagerInternal.isInteractive; import static android.os.PowerManagerInternal.wakefulnessToString; +import static android.service.dreams.Flags.allowDreamWhenPostured; import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle; @@ -216,6 +217,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_ATTENTIVE = 1 << 14; // Dirty bit: display group wakefulness has changed private static final int DIRTY_DISPLAY_GROUP_WAKEFULNESS = 1 << 16; + // Dirty bit: device postured state has changed + private static final int DIRTY_POSTURED_STATE = 1 << 17; // Summarizes the state of all active wakelocks. static final int WAKE_LOCK_CPU = 1 << 0; @@ -500,6 +503,11 @@ public final class PowerManagerService extends SystemService // The current dock state. private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + /** + * Whether the device is upright and stationary. + */ + private boolean mDevicePostured; + // True to decouple auto-suspend mode from the display state. private boolean mDecoupleHalAutoSuspendModeFromDisplayConfig; @@ -530,6 +538,9 @@ public final class PowerManagerService extends SystemService // Default value for dreams activate-on-dock private boolean mDreamsActivatedOnDockByDefaultConfig; + /** Default value for whether dreams are activated when postured (stationary + upright) */ + private boolean mDreamsActivatedWhilePosturedByDefaultConfig; + // True if dreams can run while not plugged in. private boolean mDreamsEnabledOnBatteryConfig; @@ -558,6 +569,9 @@ public final class PowerManagerService extends SystemService // True if dreams should be activated on dock. private boolean mDreamsActivateOnDockSetting; + /** Whether dreams should be activated when device is postured (stationary and upright) */ + private boolean mDreamsActivateWhilePosturedSetting; + // True if doze should not be started until after the screen off transition. private boolean mDozeAfterScreenOff; @@ -1471,6 +1485,9 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED), + false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.SCREEN_OFF_TIMEOUT), false, mSettingsObserver, UserHandle.USER_ALL); @@ -1549,6 +1566,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); mDreamsActivatedOnDockByDefaultConfig = resources.getBoolean( com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamsActivatedWhilePosturedByDefaultConfig = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault); mDreamsEnabledOnBatteryConfig = resources.getBoolean( com.android.internal.R.bool.config_dreamsEnabledOnBattery); mDreamsBatteryLevelMinimumWhenPoweredConfig = resources.getInteger( @@ -1589,6 +1608,10 @@ public final class PowerManagerService extends SystemService Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, mDreamsActivatedOnDockByDefaultConfig ? 1 : 0, UserHandle.USER_CURRENT) != 0); + mDreamsActivateWhilePosturedSetting = (Settings.Secure.getIntForUser(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + mDreamsActivatedWhilePosturedByDefaultConfig ? 1 : 0, + UserHandle.USER_CURRENT) != 0); mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver, Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT, UserHandle.USER_CURRENT); @@ -3336,7 +3359,7 @@ public final class PowerManagerService extends SystemService if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS - | DIRTY_SCREEN_BRIGHTNESS_BOOST)) == 0) { + | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_POSTURED_STATE)) == 0) { return changed; } final long time = mClock.uptimeMillis(); @@ -3375,7 +3398,8 @@ public final class PowerManagerService extends SystemService private boolean shouldNapAtBedTimeLocked() { return mDreamsActivateOnSleepSetting || (mDreamsActivateOnDockSetting - && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED); + && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) + || (mDreamsActivateWhilePosturedSetting && mDevicePostured); } /** @@ -4489,6 +4513,17 @@ public final class PowerManagerService extends SystemService } } + private void setDevicePosturedInternal(boolean isPostured) { + if (!allowDreamWhenPostured()) { + return; + } + synchronized (mLock) { + mDevicePostured = isPostured; + mDirty |= DIRTY_POSTURED_STATE; + updatePowerStateLocked(); + } + } + private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) { synchronized (mLock) { if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) { @@ -4794,6 +4829,8 @@ public final class PowerManagerService extends SystemService + mDreamsActivatedOnSleepByDefaultConfig); pw.println(" mDreamsActivatedOnDockByDefaultConfig=" + mDreamsActivatedOnDockByDefaultConfig); + pw.println(" mDreamsActivatedWhilePosturedByDefaultConfig=" + + mDreamsActivatedWhilePosturedByDefaultConfig); pw.println(" mDreamsEnabledOnBatteryConfig=" + mDreamsEnabledOnBatteryConfig); pw.println(" mDreamsBatteryLevelMinimumWhenPoweredConfig=" @@ -4805,6 +4842,8 @@ public final class PowerManagerService extends SystemService pw.println(" mDreamsEnabledSetting=" + mDreamsEnabledSetting); pw.println(" mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting); pw.println(" mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting); + pw.println(" mDreamsActivateWhilePosturedSetting=" + + mDreamsActivateWhilePosturedSetting); pw.println(" mDozeAfterScreenOff=" + mDozeAfterScreenOff); pw.println(" mBrightWhenDozingConfig=" + mBrightWhenDozingConfig); pw.println(" mMinimumScreenOffTimeoutConfig=" + mMinimumScreenOffTimeoutConfig); @@ -7388,6 +7427,11 @@ public final class PowerManagerService extends SystemService public boolean isAmbientDisplaySuppressed() { return mAmbientDisplaySuppressionController.isSuppressed(); } + + @Override + public void setDevicePostured(boolean isPostured) { + setDevicePosturedInternal(isPostured); + } } /** diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java index 6798a6146ae0..2452dc59bea5 100644 --- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java +++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java @@ -17,6 +17,7 @@ package com.android.server.security.authenticationpolicy; import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE; +import static android.security.Flags.disableAdaptiveAuthCounterLock; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; @@ -39,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.provider.Settings; import android.security.authenticationpolicy.AuthenticationPolicyManager; import android.security.authenticationpolicy.DisableSecureLockDeviceParams; import android.security.authenticationpolicy.EnableSecureLockDeviceParams; @@ -251,6 +253,17 @@ public class AuthenticationPolicyService extends SystemService { return; } + if (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE) { + final boolean disabled = Settings.Secure.getIntForUser( + getContext().getContentResolver(), + Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, + 0 /* default */, userId) != 0; + if (disabled) { + Slog.d(TAG, "not locking (disabled by user)"); + return; + } + } + //TODO: additionally consider the trust signal before locking device lockDevice(userId); } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 40ea9319c6be..7f2c68ff60b1 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -118,6 +118,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.health.connect.HealthConnectManager; import android.media.AudioManager; import android.media.MediaDrm; import android.media.UnsupportedSchemeException; @@ -4115,7 +4116,7 @@ public class StatsPullAtomService extends SystemService { int nOps = opsList.size(); for (int i = 0; i < nOps; i++) { AppOpEntry entry = opsList.get(i); - if (entry.mHash >= samplingRate) { + if (entry.mHash >= samplingRate || isHealthAppOp(entry.mOp.getOpCode())) { continue; } StatsEvent e; @@ -4301,6 +4302,11 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + if (isHealthAppOp(AppOpsManager.strOpToOp(message.getOp()))) { + // Not log sensitive health app ops. + return StatsManager.PULL_SKIP; + } + pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, message.getUid(), message.getPackageName(), "", message.getAttributionTag() == null ? "" : message.getAttributionTag(), @@ -4893,7 +4899,7 @@ public class StatsPullAtomService extends SystemService { Slog.e(TAG, "Disconnected from keystore service. Cannot pull.", e); return StatsManager.PULL_SKIP; } catch (ServiceSpecificException e) { - Slog.e(TAG, "pulling keystore metrics failed", e); + Slog.e(TAG, "Pulling keystore atom with tag " + atomTag + " failed", e); return StatsManager.PULL_SKIP; } finally { Binder.restoreCallingIdentity(callingToken); @@ -5370,6 +5376,11 @@ public class StatsPullAtomService extends SystemService { } } + private boolean isHealthAppOp(int opCode) { + String permission = AppOpsManager.opToPermission(opCode); + return permission != null && HealthConnectManager.isHealthPermission(mContext, permission); + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0226650ec560..42a47d4a037e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8440,8 +8440,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTmpConfig.updateFrom(resolvedConfig); newParentConfiguration = mTmpConfig; } - - mAppCompatController.getAspectRatioPolicy().reset(); + final AppCompatAspectRatioPolicy aspectRatioPolicy = + mAppCompatController.getAspectRatioPolicy(); + aspectRatioPolicy.reset(); mIsEligibleForFixedOrientationLetterbox = false; mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration, isFixedRotationTransforming()); @@ -8472,12 +8473,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. - if (!mAppCompatController.getAspectRatioPolicy() - .isLetterboxedForFixedOrientationAndAspectRatio() - && !mAppCompatController.getAspectRatioOverrides() - .hasFullscreenOverride()) { - resolveAspectRatioRestriction(newParentConfiguration); - } + aspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded(newParentConfiguration); final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets(); final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController.getSizeCompatModePolicy(); @@ -8509,8 +8505,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Fixed orientation letterboxing is possible on both large screen devices // with ignoreOrientationRequest enabled and on phones in split screen even with // ignoreOrientationRequest disabled. - && (mAppCompatController.getAspectRatioPolicy() - .isLetterboxedForFixedOrientationAndAspectRatio() + && (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio() // Limiting check for aspect ratio letterboxing to devices with enabled // ignoreOrientationRequest. This avoids affecting phones where apps may // not expect the change of smallestScreenWidthDp after rotation which is @@ -8518,7 +8513,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // accurate on phones shouldn't make the big difference and is expected // to be already well-tested by apps. || (isIgnoreOrientationRequest - && mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied()))) { + && aspectRatioPolicy.isAspectRatioApplied()))) { // TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all // rotations and only re-calculate if parent bounds have non-orientation size change. resolvedConfig.smallestScreenWidthDp = @@ -9007,37 +9002,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A new Rect(resolvedBounds)); } - /** - * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by - * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition - * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has - * higher priority than the requested override bounds. - */ - private void resolveAspectRatioRestriction(Configuration newParentConfiguration) { - final Configuration resolvedConfig = getResolvedOverrideConfiguration(); - final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); - // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use - // restricted size (resolved bounds may be the requested override bounds). - mTmpBounds.setEmpty(); - final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController - .getAspectRatioPolicy(); - aspectRatioPolicy.applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds); - // If the out bounds is not empty, it means the activity cannot fill parent's app bounds, - // then they should be aligned later in #updateResolvedBoundsPosition() - if (!mTmpBounds.isEmpty()) { - resolvedBounds.set(mTmpBounds); - } - if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { - // Compute the configuration based on the resolved bounds. If aspect ratio doesn't - // restrict, the bounds should be the requested override bounds. - mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo(); - computeConfigByResolveHint(resolvedConfig, newParentConfiguration); - aspectRatioPolicy.setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds)); - } - } - @Override public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index 4ecd0bec9880..ab1778a1a32e 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -56,6 +56,8 @@ class AppCompatAspectRatioPolicy { @NonNull private final AppCompatAspectRatioState mAppCompatAspectRatioState; + private final Rect mTmpBounds = new Rect(); + AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord, @NonNull TransparentPolicy transparentPolicy, @NonNull AppCompatOverrides appCompatOverrides) { @@ -222,6 +224,45 @@ class AppCompatAspectRatioPolicy { return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; } + /** + * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by + * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition} + * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has + * higher priority than the requested override bounds. + */ + void resolveAspectRatioRestrictionIfNeeded(@NonNull Configuration newParentConfiguration) { + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration. + // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. + if (isLetterboxedForFixedOrientationAndAspectRatio() + || getOverrides().hasFullscreenOverride()) { + return; + } + final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration(); + final Rect parentAppBounds = + mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride; + final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + // Use tmp bounds to calculate aspect ratio so we can know whether the activity should + // use restricted size (resolved bounds may be the requested override bounds). + mTmpBounds.setEmpty(); + applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds); + // If the out bounds is not empty, it means the activity cannot fill parent's app + // bounds, then they should be aligned later in #updateResolvedBoundsPosition(). + if (!mTmpBounds.isEmpty()) { + resolvedBounds.set(mTmpBounds); + } + if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { + // Compute the configuration based on the resolved bounds. If aspect ratio doesn't + // restrict, the bounds should be the requested override bounds. + // TODO(b/384473893): Improve ActivityRecord usage here. + mActivityRecord.mResolveConfigHint.mTmpOverrideDisplayInfo = + mActivityRecord.getFixedRotationTransformDisplayInfo(); + mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration); + setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds)); + } + } + private boolean isParentFullscreenPortrait() { final WindowContainer<?> parent = mActivityRecord.getParent(); return parent != null @@ -364,6 +405,11 @@ class AppCompatAspectRatioPolicy { && !dc.getIgnoreOrientationRequest(); } + @NonNull + private AppCompatAspectRatioOverrides getOverrides() { + return mActivityRecord.mAppCompatController.getAspectRatioOverrides(); + } + private static class AppCompatAspectRatioState { // Whether the aspect ratio restrictions applied to the activity bounds // in applyAspectRatio(). diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index a94f6252cd68..cc9cd90fae06 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -39,7 +39,7 @@ class AppCompatController { @NonNull private final AppCompatOverrides mAppCompatOverrides; @NonNull - private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery; + private final AppCompatDeviceStateQuery mDeviceStateQuery; @NonNull private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; @NonNull @@ -50,11 +50,11 @@ class AppCompatController { final PackageManager packageManager = wmService.mContext.getPackageManager(); final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, activityRecord.packageName); - mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord); + mDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord); mTransparentPolicy = new TransparentPolicy(activityRecord, wmService.mAppCompatConfiguration); mAppCompatOverrides = new AppCompatOverrides(activityRecord, packageManager, - wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery); + wmService.mAppCompatConfiguration, optPropBuilder, mDeviceStateQuery); mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides); mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord, mTransparentPolicy, mAppCompatOverrides); @@ -129,8 +129,8 @@ class AppCompatController { } @NonNull - AppCompatDeviceStateQuery getAppCompatDeviceStateQuery() { - return mAppCompatDeviceStateQuery; + AppCompatDeviceStateQuery getDeviceStateQuery() { + return mDeviceStateQuery; } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java index 087edc184b6f..a7c52bd1fc38 100644 --- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java @@ -107,7 +107,7 @@ class AppCompatReachabilityPolicy { return; } final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController - .getAppCompatDeviceStateQuery(); + .getDeviceStateQuery(); final boolean isInFullScreenBookMode = deviceStateQuery .isDisplayFullScreenAndSeparatingHinge() && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); @@ -153,7 +153,7 @@ class AppCompatReachabilityPolicy { return; } final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController - .getAppCompatDeviceStateQuery(); + .getDeviceStateQuery(); final boolean isInFullScreenTabletopMode = deviceStateQuery .isDisplayFullScreenAndSeparatingHinge(); final int letterboxPositionForVerticalReachability = mAppCompatConfiguration diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index 601b17c46c03..576e5d5d0cd2 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -51,6 +51,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import android.view.ContextThemeWrapper; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; @@ -498,10 +499,21 @@ class AppWarnings { } } if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) { + Context context = getUiContextForActivity(ar); + // PageSizeMismatchDialog has link in message which should open in browser. + // Starting activity from non-activity context is not allowed and flag + // FLAG_ACTIVITY_NEW_TASK is needed to start activity. + context = new ContextThemeWrapper(context, context.getThemeResId()) { + @Override + public void startActivity(Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + super.startActivity(intent); + } + }; pageSizeMismatchDialog = new PageSizeMismatchDialog( AppWarnings.this, - getUiContextForActivity(ar), + context, ar.info.applicationInfo, userId, warning); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 5bec4424269a..4b30a43db5d9 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -53,6 +53,12 @@ import java.util.List; * Policy that manages {@link DisplayArea}. */ public abstract class DisplayAreaPolicy { + /** + * No corresponding use case yet (see b/154719717). The current implementation still uses + * {@link WindowState#shouldMagnify}. + */ + static final boolean USE_DISPLAY_AREA_FOR_FULLSCREEN_MAGNIFICATION = false; + protected final WindowManagerService mWmService; /** @@ -161,14 +167,17 @@ public abstract class DisplayAreaPolicy { TYPE_STATUS_BAR, TYPE_NOTIFICATION_SHADE, TYPE_WALLPAPER) .build()); } + if (USE_DISPLAY_AREA_FOR_FULLSCREEN_MAGNIFICATION) { + rootHierarchy.addFeature( + new Feature.Builder(wmService.mPolicy, "FullscreenMagnification", + FEATURE_FULLSCREEN_MAGNIFICATION) + .all() + .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD, + TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY, + TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL) + .build()); + } rootHierarchy - .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification", - FEATURE_FULLSCREEN_MAGNIFICATION) - .all() - .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD, - TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY, - TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL) - .build()) .addFeature(new Feature.Builder(wmService.mPolicy, "ImePlaceholder", FEATURE_IME_PLACEHOLDER) .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index fa748d3a22a5..c418349e6158 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -28,9 +28,11 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.content.ClipData; import android.content.Context; +import android.hardware.display.DisplayTopology; import android.hardware.input.InputManagerGlobal; import android.os.Binder; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -50,12 +52,14 @@ import android.window.IUnhandledDragCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.WindowManagerInternal.IDragDropCallback; +import com.android.window.flags.Flags; import java.util.Objects; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; /** * Managing drag and drop operations initiated by View#startDragAndDrop. @@ -83,6 +87,8 @@ class DragDropController { private WindowManagerService mService; private final Handler mHandler; + private final Consumer<DisplayTopology> mDisplayTopologyListener = + this::handleDisplayTopologyChange; // The global drag listener for handling cross-window drags private IGlobalDragListener mGlobalDragListener; @@ -108,6 +114,10 @@ class DragDropController { DragDropController(WindowManagerService service, Looper looper) { mService = service; mHandler = new DragHandler(service, looper); + if (Flags.enableConnectedDisplaysDnd()) { + mService.mDisplayManager.registerTopologyListener( + new HandlerExecutor(mService.mH), mDisplayTopologyListener); + } } @VisibleForTesting @@ -481,6 +491,19 @@ class DragDropController { } } + @VisibleForTesting + void handleDisplayTopologyChange(DisplayTopology unused) { + synchronized (mService.mGlobalLock) { + if (mDragState == null) { + return; + } + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "DisplayTopology changed, cancelling DragAndDrop"); + } + cancelDragAndDrop(mDragState.mToken, true /* skipAnimation */); + } + } + /** * Handles motion events. * @param keepHandling Whether if the drag operation is continuing or this is the last motion diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java index 02a7db19f405..0fbf56d120a8 100644 --- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java @@ -77,8 +77,9 @@ class EmulatorDisplayOverlay { mOverlay = context.getDrawable( com.android.internal.R.drawable.emulator_circular_window_overlay); - mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, mScreenSize.x, - mScreenSize.y, PixelFormat.RGBA_8888); + mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true); + mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y, + PixelFormat.RGBA_8888); mSurface = mBlastBufferQueue.createSurface(); } diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java index 29922f0f85c5..24235ef2d585 100644 --- a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java +++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java @@ -24,8 +24,10 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.text.Html; +import android.text.method.LinkMovementMethod; import android.view.Window; import android.view.WindowManager; +import android.widget.TextView; import com.android.internal.R; @@ -69,6 +71,14 @@ class PageSizeMismatchDialog extends AppWarnings.BaseDialog { mDialog.create(); final Window window = mDialog.getWindow(); - window.setType(WindowManager.LayoutParams.TYPE_PHONE); + window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + + @Override + public void show() { + super.show(); + // Make the links in dialog clickable + final TextView msgTxt = (TextView) mDialog.findViewById(android.R.id.message); + msgTxt.setMovementMethod(LinkMovementMethod.getInstance()); } } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 44f000da3d73..b9550feeab8a 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -40,7 +40,6 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS; -import static com.android.launcher3.Flags.enableUseTopVisibleActivityForExcludeFromRecentTask; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS; @@ -1528,12 +1527,7 @@ class RecentTasks { } // The Recents is only supported on default display now, we should only keep the // most recent task of home display. - boolean isMostRecentTask; - if (enableUseTopVisibleActivityForExcludeFromRecentTask()) { - isMostRecentTask = task.getTopVisibleActivity() != null; - } else { - isMostRecentTask = taskIndex == 0; - } + boolean isMostRecentTask = task.getTopVisibleActivity() != null; return (task.isOnHomeDisplay() && isMostRecentTask); } } diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java index cdf6b08b1c57..b6365ad47535 100644 --- a/services/core/java/com/android/server/wm/StrictModeFlash.java +++ b/services/core/java/com/android/server/wm/StrictModeFlash.java @@ -63,8 +63,9 @@ class StrictModeFlash { mSurfaceControl = ctrl; mDrawNeeded = true; - mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, 1 /* width */, - 1 /* height */, PixelFormat.RGBA_8888); + mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true); + mBlastBufferQueue.update(mSurfaceControl, 1 /* width */, 1 /* height */, + PixelFormat.RGBA_8888); mSurface = mBlastBufferQueue.createSurface(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 295759c2fc7e..c6136f316c3e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5101,6 +5101,7 @@ class Task extends TaskFragment { mTranslucentActivityWaiting = r; mPendingConvertFromTranslucentActivity = r; mUndrawnActivitiesBelowTopTranslucent.clear(); + updateTaskDescription(); mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); } @@ -5110,6 +5111,7 @@ class Task extends TaskFragment { + " but is " + r); } mPendingConvertFromTranslucentActivity = null; + updateTaskDescription(); } /** @@ -5297,40 +5299,29 @@ class Task extends TaskFragment { return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea()); } - void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask, - boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { - Task rTask = r.getTask(); + void startActivityLocked(@NonNull ActivityRecord r, @Nullable Task topTask, boolean newTask, + boolean isTaskSwitch, @Nullable ActivityOptions options, + @Nullable ActivityRecord sourceRecord) { final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront(); - final boolean isOrhasTask = rTask == this || hasChild(rTask); + final Task activityTask = r.getTask(); + final boolean isThisOrHasChildTask = activityTask == this || hasChild(activityTask); + // mLaunchTaskBehind tasks get placed at the back of the task stack. - if (!r.mLaunchTaskBehind && allowMoveToFront && (!isOrhasTask || newTask)) { + if (!r.mLaunchTaskBehind && allowMoveToFront && (!isThisOrHasChildTask || newTask)) { // Last activity in task had been removed or ActivityManagerService is reusing task. // Insert or replace. // Might not even be in. - positionChildAtTop(rTask); + positionChildAtTop(activityTask); } - Task task = null; - if (!newTask && isOrhasTask && !r.shouldBeVisible()) { + + if (!newTask && isThisOrHasChildTask && !r.shouldBeVisible()) { ActivityOptions.abort(options); return; } - // Place a new activity at top of root task, so it is next to interact with the user. - - // If we are not placing the new activity frontmost, we do not want to deliver the - // onUserLeaving callback to the actual frontmost activity - final Task activityTask = r.getTask(); - if (task == activityTask && mChildren.indexOf(task) != (getChildCount() - 1)) { - mTaskSupervisor.mUserLeaving = false; - if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, - "startActivity() behind front, mUserLeaving=false"); - } - - task = activityTask; - // Slot the activity into the history root task and proceed - ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s " - + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace()); + ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s callers: %s", r, + activityTask, new RuntimeException("here").fillInStackTrace()); if (isActivityTypeHomeOrRecents() && getActivityBelow(r) == null) { // If this is the first activity, don't do any fancy animations, @@ -5346,15 +5337,15 @@ class Task extends TaskFragment { return; } - final DisplayContent dc = mDisplayContent; - if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, - "Prepare open transition: starting " + r); + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r); + + // Place a new activity at top of root task, so it is next to interact with the user. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - dc.prepareAppTransition(TRANSIT_NONE); + mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(r); mTransitionController.setNoAnimation(r); } else { - dc.prepareAppTransition(TRANSIT_OPEN); + mDisplayContent.prepareAppTransition(TRANSIT_OPEN); mTaskSupervisor.mNoAnimActivities.remove(r); } if (newTask && !r.mLaunchTaskBehind) { @@ -5405,8 +5396,7 @@ class Task extends TaskFragment { // "has the same starting icon" as the next one. This allows the // window manager to keep the previous window it had previously // created, if it still had one. - Task baseTask = r.getTask(); - final ActivityRecord prev = baseTask.getActivity( + final ActivityRecord prev = activityTask.getActivity( a -> a.mStartingData != null && a.showToCurrentUser()); mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask, isTaskSwitch, sourceRecord); diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java index 9780d3317e11..eb6eeb31e8fb 100644 --- a/services/core/java/com/android/server/wm/Watermark.java +++ b/services/core/java/com/android/server/wm/Watermark.java @@ -126,8 +126,9 @@ class Watermark { } catch (OutOfResourcesException e) { } mSurfaceControl = ctrl; - mBlastBufferQueue = new BLASTBufferQueue(TITLE, mSurfaceControl, 1 /* width */, - 1 /* height */, PixelFormat.RGBA_8888); + mBlastBufferQueue = new BLASTBufferQueue(TITLE, /* updateDestinationFrame */ true); + mBlastBufferQueue.update(mSurfaceControl, 1 /* width */, 1 /* height */, + PixelFormat.RGBA_8888); mSurface = mBlastBufferQueue.createSurface(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 36d52ddb40e6..1754d7346220 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -753,8 +753,6 @@ public class WindowManagerService extends IWindowManager.Stub final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2; int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; - /** Indicates that the system server is actively demanding the screen be frozen. */ - boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; @VisibleForTesting @@ -3354,60 +3352,6 @@ public class WindowManagerService extends IWindowManager.Stub return getDefaultDisplayContentLocked().mAppTransition.isIdle(); } - - // ------------------------------------------------------------- - // Misc IWindowSession methods - // ------------------------------------------------------------- - - /** Freeze the screen during a user-switch event. Called by UserController. */ - @Override - public void startFreezingScreen(int exitAnim, int enterAnim) { - if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN, - "startFreezingScreen()")) { - throw new SecurityException("Requires FREEZE_SCREEN permission"); - } - - synchronized (mGlobalLock) { - if (!mClientFreezingScreen) { - mClientFreezingScreen = true; - final long origId = Binder.clearCallingIdentity(); - try { - startFreezingDisplay(exitAnim, enterAnim); - mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); - mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - } - - /** - * No longer actively demand that the screen remain frozen. - * Called by UserController after a user-switch. - * This doesn't necessarily immediately unlock the screen; it just allows it if we're ready. - */ - @Override - public void stopFreezingScreen() { - if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN, - "stopFreezingScreen()")) { - throw new SecurityException("Requires FREEZE_SCREEN permission"); - } - - synchronized (mGlobalLock) { - if (mClientFreezingScreen) { - mClientFreezingScreen = false; - mLastFinishedFreezeSource = "client"; - final long origId = Binder.clearCallingIdentity(); - try { - stopFreezingDisplayLocked(); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - } - @Override public void disableKeyguard(IBinder token, String tag, int userId) { userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), @@ -5669,7 +5613,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; public static final int SHOW_STRICT_MODE_VIOLATION = 25; - public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; public static final int NEW_ANIMATOR_SCALE = 34; @@ -5759,17 +5702,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case CLIENT_FREEZE_TIMEOUT: { - synchronized (mGlobalLock) { - if (mClientFreezingScreen) { - mClientFreezingScreen = false; - mLastFinishedFreezeSource = "client-timeout"; - stopFreezingDisplayLocked(); - } - } - break; - } - case REPORT_WINDOWS_CHANGE: { if (mWindowsChanged) { synchronized (mGlobalLock) { @@ -6552,14 +6484,14 @@ public class WindowManagerService extends IWindowManager.Stub } if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0 || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE - || mClientFreezingScreen || numOpeningApps > 0) { + || numOpeningApps > 0) { ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning " + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, " + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, " - + "mClientFreezingScreen=%b, mOpeningApps.size()=%d", + + "mOpeningApps.size()=%d", waitingForConfig, waitingForRemoteDisplayChange, mAppsFreezingScreen, mWindowsFreezingScreen, - mClientFreezingScreen, numOpeningApps); + numOpeningApps); return; } @@ -6589,7 +6521,6 @@ public class WindowManagerService extends IWindowManager.Stub } ProtoLog.i(WM_ERROR, "%s", sb.toString()); mH.removeMessages(H.APP_FREEZE_TIMEOUT); - mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); if (PROFILE_ORIENTATION) { Debug.stopMethodTracing(); } @@ -7096,7 +7027,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mTransactionSequence="); pw.println(mTransactionSequence); pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen); pw.print(" windows="); pw.print(mWindowsFreezingScreen); - pw.print(" client="); pw.print(mClientFreezingScreen); pw.print(" apps="); pw.println(mAppsFreezingScreen); final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked(); pw.print(" mRotation="); pw.println(defaultDisplayContent.getRotation()); diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index c0bc8e094a39..2aa0c6b6dd0b 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -258,12 +258,33 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential if (propagateCancellation) { mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); } - mRequestSessionMetric.logApiCalledAtFinish(apiStatus); mRequestSessionStatus = RequestSessionStatus.COMPLETE; + if (Flags.fixMetricDuplicationEmits()) { + logTrackOneCandidatesAndPrepareFinalPhaseLogs(apiStatus); + } + mRequestSessionMetric.logApiCalledAtFinish(apiStatus); mProviders.clear(); clearRequestSessionLocked(); } + /** + * Ensures all logging done in final phase methods only occur within the 'finishSession'. + */ + private void logTrackOneCandidatesAndPrepareFinalPhaseLogs(int apiStatus) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); + if (isRespondingWithError(apiStatus)) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + } else if (isRespondingWithUserCanceledError(apiStatus)) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/false, ProviderStatusForMetrics.FINAL_FAILURE + ); + } else if (isRespondingWithSuccess(apiStatus)) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*hasException=*/ false, + ProviderStatusForMetrics.FINAL_SUCCESS); + } + } + void cancelExistingPendingIntent() { if (mPendingIntent != null) { try { @@ -343,9 +364,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential * @param response the response associated with the API call that just completed */ protected void respondToClientWithResponseAndFinish(V response) { - mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); - mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false, - ProviderStatusForMetrics.FINAL_SUCCESS); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*hasException=*/ false, + ProviderStatusForMetrics.FINAL_SUCCESS); + } if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { Slog.w(TAG, "Request has already been completed. This is strange."); return; @@ -360,8 +383,10 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential finishSession(/*propagateCancellation=*/false, ApiStatus.SUCCESS.getMetricCode()); } catch (RemoteException e) { - mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( - /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + } Slog.e(TAG, "Issue while responding to client with a response : " + e); finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode()); } @@ -374,9 +399,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential * @param errorMsg the error message given back in the flow */ protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { - mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); - mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( - /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); + mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( + /*hasException=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); + } if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { Slog.w(TAG, "Request has already been completed. This is strange."); return; @@ -385,7 +412,6 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential finishSession(/*propagateCancellation=*/true, ApiStatus.CLIENT_CANCELED.getMetricCode()); return; } - try { invokeClientCallbackError(errorType, errorMsg); } catch (RemoteException e) { @@ -393,7 +419,9 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING); if (isUserCanceled) { - mRequestSessionMetric.setHasExceptionFinalPhase(/* has_exception */ false); + if (!Flags.fixMetricDuplicationEmits()) { + mRequestSessionMetric.setHasExceptionFinalPhase(/* hasException */ false); + } finishSession(/*propagateCancellation=*/false, ApiStatus.USER_CANCELED.getMetricCode()); } else { @@ -421,4 +449,26 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode()); } } + + /** + * This captures the final state of the apiStatus as presented in 'finishSession'. + */ + private boolean isRespondingWithError(int apiStatus) { + return apiStatus == ApiStatus.FAILURE.getMetricCode() + || apiStatus == ApiStatus.CLIENT_CANCELED.getMetricCode(); + } + + /** + * A unique failure case, where we do not set the exception bit to be true. + */ + private boolean isRespondingWithUserCanceledError(int apiStatus) { + return apiStatus == ApiStatus.USER_CANCELED.getMetricCode(); + } + + /** + * This captures the final state of the apiStatus as presented in 'finishSession'. + */ + private boolean isRespondingWithSuccess(int apiStatus) { + return apiStatus == ApiStatus.SUCCESS.getMetricCode(); + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index fadab1f8832e..25e9f8a38f89 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1527,6 +1527,8 @@ public final class SystemServer implements Dumpable { boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice", false); + boolean isDesktop = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC); + boolean isWatch = RoSystemFeatures.hasFeatureWatch(context); boolean isArc = context.getPackageManager().hasSystemFeature( @@ -1656,7 +1658,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - if (!isTv) { + if (!isTv && !isDesktop) { t.traceBegin("StartVibratorManagerService"); mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); t.traceEnd(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index f5bed999d5a0..5393e20889c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -455,8 +455,9 @@ public class LocalDisplayAdapterTest { * Confirm that external display uses physical density. */ @Test - public void testDpiValues() throws Exception { + public void testDpiValues_baseDensityForExternalDisplaysDisabled() throws Exception { // needs default one always + doReturn(false).when(mFlags).isBaseDensityForExternalDisplaysEnabled(); setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_B)); updateAvailableDisplays(); @@ -472,6 +473,25 @@ public class LocalDisplayAdapterTest { 16000); } + @Test + public void testDpiValues_baseDensityForExternalDisplaysEnabled() throws Exception { + // needs default one always + doReturn(true).when(mFlags).isBaseDensityForExternalDisplaysEnabled(); + setUpDisplay(new FakeDisplay(PORT_A)); + setUpDisplay(new FakeDisplay(PORT_B)); + updateAvailableDisplays(); + mAdapter.registerLocked(); + + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertDisplayDpi( + mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100, + 100); + assertDisplayDpi( + mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100, + 100); + } + private static class DisplayModeWrapper { public SurfaceControl.DisplayMode mode; public float[] expectedAlternativeRefreshRates; diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index d9256247b835..6b138b986fe7 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; +import static android.service.dreams.Flags.FLAG_ALLOW_DREAM_WHEN_POSTURED; import static com.android.server.deviceidle.Flags.FLAG_DISABLE_WAKELOCKS_IN_LIGHT_IDLE; @@ -81,8 +82,8 @@ import android.os.BatterySaverPolicyConfig; import android.os.Binder; import android.os.Handler; import android.os.IBinder; -import android.os.IWakeLockCallback; import android.os.IScreenTimeoutPolicyListener; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; @@ -120,8 +121,8 @@ import com.android.server.power.PowerManagerService.WakeLock; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; -import com.android.server.power.feature.flags.Flags; import com.android.server.power.feature.PowerManagerFlags; +import com.android.server.power.feature.flags.Flags; import com.android.server.testutils.OffsettableClock; import com.google.testing.junit.testparameterinjector.TestParameter; @@ -279,6 +280,8 @@ public class PowerManagerServiceTest { Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 0); mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); @@ -1215,6 +1218,52 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); } + @EnableFlags(FLAG_ALLOW_DREAM_WHEN_POSTURED) + @Test + public void testDreamActivateWhilePosturedEnabled_postured_afterTimeout_goesToDreaming() { + when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 1); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setMinimumScreenOffTimeoutConfig(5); + createService(); + startSystem(); + mService.getLocalServiceInstance().setDevicePostured(true); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + advanceTime(15000); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + } + + @EnableFlags(FLAG_ALLOW_DREAM_WHEN_POSTURED) + @Test + public void testDreamActivateWhilePosturedEnabled_notPostured_afterTimeout_goesToDozing() { + when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true); + Settings.Secure.putInt(mContextSpy.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 1); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + setMinimumScreenOffTimeoutConfig(5); + createService(); + startSystem(); + mService.getLocalServiceInstance().setDevicePostured(false); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + advanceTime(15000); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); + } + @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) @SuppressWarnings("GuardedBy") @Test diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java index cc5be7ebba62..1522954c123f 100644 --- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java @@ -46,17 +46,14 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemProperties; import android.provider.DeviceConfig; -import android.util.Log; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.internal.util.FrameworkStatsLog; import com.android.internal.os.IBinaryTransparencyService; -import com.android.server.pm.BackgroundInstallControlService; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.pm.BackgroundInstallControlCallbackHelper; -import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.AndroidPackageSplit; +import com.android.server.pm.BackgroundInstallControlService; import com.android.server.pm.pkg.PackageStateInternal; import org.junit.After; @@ -82,7 +79,7 @@ public class BinaryTransparencyServiceTest { private Context mContext; private BinaryTransparencyService mBinaryTransparencyService; private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface; - private DeviceConfig.Properties mOriginalBiometricsFlags; + private String mOriginalBiometricsFlag; @Mock private BinaryTransparencyService.BiometricLogger mBiometricLogger; @@ -117,17 +114,15 @@ public class BinaryTransparencyServiceTest { mBinaryTransparencyService = new BinaryTransparencyService(mContext, mBiometricLogger); mTestInterface = mBinaryTransparencyService.new BinaryTransparencyServiceImpl(); - mOriginalBiometricsFlags = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BIOMETRICS); + mOriginalBiometricsFlag = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_BIOMETRICS, + BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION); } @After - public void tearDown() throws Exception { - try { - DeviceConfig.setProperties(mOriginalBiometricsFlags); - } catch (DeviceConfig.BadConfigException e) { - Log.e(TAG, "Failed to reset biometrics flags to the original values before test. " - + e); - } + public void tearDown() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS, + BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, + mOriginalBiometricsFlag, false /* makeDefault */); LocalServices.removeServiceForTest(PackageManagerInternal.class); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index 464fee2bfc11..fb31cfe762f2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER; +import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; @@ -39,6 +40,10 @@ import android.content.Context; import android.hardware.display.DisplayManagerGlobal; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.util.SparseArray; import android.view.Display; @@ -55,12 +60,14 @@ import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; +import com.android.server.accessibility.magnification.MagnificationKeyHandler; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -88,8 +95,16 @@ public class AccessibilityInputFilterTest { | FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_FILTER_KEY_EVENTS; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + // The expected order of EventStreamTransformations. private final Class[] mExpectedEventHandlerTypes = + {MagnificationKeyHandler.class, KeyboardInterceptor.class, MotionEventInjector.class, + FullScreenMagnificationGestureHandler.class, TouchExplorer.class, + AutoclickController.class, AccessibilityInputFilter.class}; + + private final Class[] mExpectedEventHandlerTypesWithoutMagKeyboard = {KeyboardInterceptor.class, MotionEventInjector.class, FullScreenMagnificationGestureHandler.class, TouchExplorer.class, AutoclickController.class, AccessibilityInputFilter.class}; @@ -176,6 +191,7 @@ public class AccessibilityInputFilterTest { } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() { prepareLooper(); @@ -191,9 +207,9 @@ public class AccessibilityInputFilterTest { EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY); assertNotNull(next); - // Start from index 1 because KeyboardInterceptor only exists in EventHandler for - // DEFAULT_DISPLAY. - for (int i = 1; next != null; i++) { + // Start from index 2 because KeyboardInterceptor and MagnificationKeyHandler only exist in + // EventHandler for DEFAULT_DISPLAY. + for (int i = 2; next != null; i++) { assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); next = next.getNext(); } @@ -232,6 +248,7 @@ public class AccessibilityInputFilterTest { } @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() { prepareLooper(); @@ -248,10 +265,36 @@ public class AccessibilityInputFilterTest { } next = mEventHandler.get(SECOND_DISPLAY); + // Start from index 2 because KeyboardInterceptor and MagnificationKeyHandler only exist + // in EventHandler for DEFAULT_DISPLAY. + for (int i = 2; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + next = next.getNext(); + } + } + + @Test + @RequiresFlagsDisabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation_noMagKeys() { + prepareLooper(); + + setDisplayCount(2); + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + assertEquals(2, mEventHandler.size()); + + // Check if mEventHandler for each display has correct order of the + // EventStreamTransformations. + EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY); + for (int i = 0; next != null; i++) { + assertEquals(next.getClass(), mExpectedEventHandlerTypesWithoutMagKeyboard[i]); + next = next.getNext(); + } + + next = mEventHandler.get(SECOND_DISPLAY); // Start from index 1 because KeyboardInterceptor only exists in EventHandler for // DEFAULT_DISPLAY. for (int i = 1; next != null; i++) { - assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]); + assertEquals(next.getClass(), mExpectedEventHandlerTypesWithoutMagKeyboard[i]); next = next.getNext(); } } @@ -387,7 +430,6 @@ public class AccessibilityInputFilterTest { assertNotNull(handler); assertEquals(WindowMagnificationGestureHandler.class, handler.getClass()); assertEquals(nextEventStream.getClass(), handler.getNext().getClass()); - } @Test public void @@ -412,6 +454,32 @@ public class AccessibilityInputFilterTest { assertEquals(nextEventStream.getClass(), handler.getNext().getClass()); } + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationKeyHandler() { + prepareLooper(); + doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when( + mAms).getMagnificationMode(DEFAULT_DISPLAY); + + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + + MagnificationKeyHandler handler = getMagnificationKeyHandlerFromEventHandler(); + assertNotNull(handler); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + public void testEnabledFeatures_fullscreenMagnificationMode_expectedMagnificationKeyHandler() { + prepareLooper(); + doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN).when( + mAms).getMagnificationMode(DEFAULT_DISPLAY); + + mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures); + + MagnificationKeyHandler handler = getMagnificationKeyHandlerFromEventHandler(); + assertNotNull(handler); + } + private static void prepareLooper() { if (Looper.myLooper() == null) { Looper.prepare(); @@ -458,4 +526,16 @@ public class AccessibilityInputFilterTest { } return null; } + + @Nullable + private MagnificationKeyHandler getMagnificationKeyHandlerFromEventHandler() { + EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY); + while (next != null) { + if (next instanceof MagnificationKeyHandler) { + return (MagnificationKeyHandler) next; + } + next = next.getNext(); + } + return null; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java new file mode 100644 index 000000000000..acce813ff659 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java @@ -0,0 +1,242 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.testutils.MockitoUtilsKt.eq; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; + +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Test cases for {@link AutoclickController}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class AutoclickControllerTest { + + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public TestableContext mTestableContext = + new TestableContext(getInstrumentation().getContext()); + + private TestableLooper mTestableLooper; + @Mock private AccessibilityTraceManager mMockTrace; + @Mock private WindowManager mMockWindowManager; + private AutoclickController mController; + + @Before + public void setUp() { + mTestableLooper = TestableLooper.get(this); + mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager); + mController = + new AutoclickController(mTestableContext, mTestableContext.getUserId(), mMockTrace); + } + + @After + public void tearDown() { + mTestableLooper.processAllMessages(); + } + + @Test + public void onMotionEvent_lazyInitClickScheduler() { + assertNull(mController.mClickScheduler); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mClickScheduler); + } + + @Test + public void onMotionEvent_nonMouseSource_notInitClickScheduler() { + assertNull(mController.mClickScheduler); + + injectFakeNonMouseActionDownEvent(); + + assertNull(mController.mClickScheduler); + } + + @Test + public void onMotionEvent_lazyInitAutoclickSettingsObserver() { + assertNull(mController.mAutoclickSettingsObserver); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mAutoclickSettingsObserver); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() { + assertNull(mController.mAutoclickIndicatorScheduler); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mAutoclickIndicatorScheduler); + } + + @Test + @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() { + assertNull(mController.mAutoclickIndicatorScheduler); + + injectFakeMouseActionDownEvent(); + + assertNull(mController.mAutoclickIndicatorScheduler); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() { + assertNull(mController.mAutoclickIndicatorView); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mAutoclickIndicatorView); + } + + @Test + @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() { + assertNull(mController.mAutoclickIndicatorView); + + injectFakeMouseActionDownEvent(); + + assertNull(mController.mAutoclickIndicatorView); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_addAutoclickIndicatorViewToWindowManager() { + injectFakeMouseActionDownEvent(); + + verify(mMockWindowManager).addView(eq(mController.mAutoclickIndicatorView), any()); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onDestroy_flagOn_removeAutoclickIndicatorViewToWindowManager() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + verify(mMockWindowManager).removeView(mController.mAutoclickIndicatorView); + } + + @Test + public void onMotionEvent_initClickSchedulerDelayFromSetting() { + injectFakeMouseActionDownEvent(); + + int delay = + Settings.Secure.getIntForUser( + mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, + AccessibilityManager.AUTOCLICK_DELAY_DEFAULT, + mTestableContext.getUserId()); + assertEquals(delay, mController.mClickScheduler.getDelayForTesting()); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_initCursorAreaSizeFromSetting() { + injectFakeMouseActionDownEvent(); + + int size = + Settings.Secure.getIntForUser( + mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, + AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT, + mTestableContext.getUserId()); + assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting()); + } + + @Test + public void onDestroy_clearClickScheduler() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + assertNull(mController.mClickScheduler); + } + + @Test + public void onDestroy_clearAutoclickSettingsObserver() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + assertNull(mController.mAutoclickSettingsObserver); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onDestroy_flagOn_clearAutoclickIndicatorScheduler() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + assertNull(mController.mAutoclickIndicatorScheduler); + } + + private void injectFakeMouseActionDownEvent() { + MotionEvent event = getFakeMotionDownEvent(); + event.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(event, event, /* policyFlags= */ 0); + } + + private void injectFakeNonMouseActionDownEvent() { + MotionEvent event = getFakeMotionDownEvent(); + event.setSource(InputDevice.SOURCE_KEYBOARD); + mController.onMotionEvent(event, event, /* policyFlags= */ 0); + } + + private MotionEvent getFakeMotionDownEvent() { + return MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ MotionEvent.ACTION_DOWN, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java new file mode 100644 index 000000000000..d1ef33d8fb70 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_DOWN; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_LEFT; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_RIGHT; +import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_UP; +import static com.android.server.accessibility.magnification.MagnificationController.ZOOM_DIRECTION_IN; +import static com.android.server.accessibility.magnification.MagnificationController.ZOOM_DIRECTION_OUT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.Display; +import android.view.KeyEvent; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.accessibility.EventStreamTransformation; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link MagnificationKeyHandler}. + */ +@RunWith(AndroidJUnit4.class) +@RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) +public class MagnificationKeyHandlerTest { + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private MagnificationKeyHandler mMkh; + + @Mock + MagnificationKeyHandler.Callback mCallback; + + @Mock + EventStreamTransformation mNextHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mMkh = new MagnificationKeyHandler(mCallback); + mMkh.setNext(mNextHandler); + } + + @Test + public void onKeyEvent_unusedKeyPress_sendToNext() { + final KeyEvent event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_L, 0, 0); + mMkh.onKeyEvent(event, 0); + + // No callbacks were called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The event was passed on. + verify(mNextHandler, times(1)).onKeyEvent(event, 0); + } + + @Test + public void onKeyEvent_arrowKeyPressWithIncorrectModifiers_sendToNext() { + final KeyEvent event = + new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, + 0, KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(event, 0); + + // No callbacks were called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The event was passed on. + verify(mNextHandler, times(1)).onKeyEvent(event, 0); + } + + @Test + public void onKeyEvent_unusedKeyPressWithCorrectModifiers_sendToNext() { + final KeyEvent event = + new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_J, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(event, 0); + + // No callbacks were called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The event was passed on. + verify(mNextHandler, times(1)).onKeyEvent(event, 0); + } + + @Test + public void onKeyEvent_panStartAndEnd_left() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_LEFT, PAN_DIRECTION_LEFT); + } + + @Test + public void onKeyEvent_panStartAndEnd_right() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_RIGHT, PAN_DIRECTION_RIGHT); + } + + @Test + public void onKeyEvent_panStartAndEnd_up() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_UP, PAN_DIRECTION_UP); + } + + @Test + public void onKeyEvent_panStartAndEnd_down() { + testPanMagnification(KeyEvent.KEYCODE_DPAD_DOWN, PAN_DIRECTION_DOWN); + } + + @Test + public void onKeyEvent_scaleStartAndEnd_zoomIn() { + testScaleMagnification(KeyEvent.KEYCODE_EQUALS, ZOOM_DIRECTION_IN); + } + + @Test + public void onKeyEvent_scaleStartAndEnd_zoomOut() { + testScaleMagnification(KeyEvent.KEYCODE_MINUS, ZOOM_DIRECTION_OUT); + } + + @Test + public void onKeyEvent_panStartAndStop_diagonal() { + final KeyEvent downLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downLeftEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + // Also press the down arrow key. + final KeyEvent downDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downDownEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + // Lift the left arrow key. + final KeyEvent upLeftEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upLeftEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(0)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + + // Lift the down arrow key. + final KeyEvent upDownEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upDownEvent, 0); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_LEFT); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, + PAN_DIRECTION_DOWN); + + // The event was not passed on. + verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); + } + + private void testPanMagnification(int keyCode, int panDirection) { + final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downEvent, 0); + + // Pan started. + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upEvent, 0); + + // Pan ended. + verify(mCallback, times(1)).onPanMagnificationStart(Display.DEFAULT_DISPLAY, panDirection); + verify(mCallback, times(1)).onPanMagnificationStop(Display.DEFAULT_DISPLAY, panDirection); + + // Scale callbacks were not called. + verify(mCallback, times(0)).onScaleMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + // The events were not passed on. + verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); + } + + private void testScaleMagnification(int keyCode, int zoomDirection) { + final KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(downEvent, 0); + + // Scale started. + verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, + zoomDirection); + verify(mCallback, times(0)).onScaleMagnificationStop(anyInt(), anyInt()); + + final KeyEvent upEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, + KeyEvent.META_META_ON | KeyEvent.META_ALT_ON); + mMkh.onKeyEvent(upEvent, 0); + + // Scale ended. + verify(mCallback, times(1)).onScaleMagnificationStart(Display.DEFAULT_DISPLAY, + zoomDirection); + verify(mCallback, times(1)).onScaleMagnificationStop(Display.DEFAULT_DISPLAY, + zoomDirection); + + // Pan callbacks were not called. + verify(mCallback, times(0)).onPanMagnificationStart(anyInt(), anyInt()); + verify(mCallback, times(0)).onPanMagnificationStop(anyInt(), anyInt()); + + // The events were not passed on. + verify(mNextHandler, times(0)).onKeyEvent(any(), anyInt()); + + } + +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index eb4a628e14e5..792faab5b196 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -25,6 +25,7 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_DEVICE_POWER_ON; +import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_POWER_STATE_CHANGE; import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_REPORT_POWER_STATUS; import static com.google.common.truth.Truth.assertThat; @@ -230,11 +231,15 @@ public class DeviceSelectActionFromTvTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -249,10 +254,14 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/false); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed); @@ -261,6 +270,7 @@ public class DeviceSelectActionFromTvTest { action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -275,8 +285,11 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/false); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); @@ -288,6 +301,7 @@ public class DeviceSelectActionFromTvTest { assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -302,8 +316,11 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/false); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); @@ -316,6 +333,7 @@ public class DeviceSelectActionFromTvTest { action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS); // Give up getting power status, and just send <Set Stream Path> mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -332,7 +350,10 @@ public class DeviceSelectActionFromTvTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -348,11 +369,15 @@ public class DeviceSelectActionFromTvTest { "testDeviceSelect"); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } @@ -369,10 +394,14 @@ public class DeviceSelectActionFromTvTest { /*isCec20=*/true); action.start(); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); action.processCommand(REPORT_POWER_STATUS_STANDBY); mTestLooper.dispatchAll(); + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(userControlPressed); @@ -381,6 +410,7 @@ public class DeviceSelectActionFromTvTest { action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); action.processCommand(REPORT_POWER_STATUS_ON); mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java index ee8eb9b35088..b76e0bc8cd14 100644 --- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java @@ -42,8 +42,10 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.events.AuthenticationFailedInfo; import android.hardware.biometrics.events.AuthenticationSucceededInfo; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; @@ -151,6 +153,8 @@ public class AuthenticationPolicyServiceTest { when(mSecureLockDeviceService.disableSecureLockDevice(any())) .thenReturn(ERROR_UNSUPPORTED); } + + toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */); } @After @@ -252,8 +256,24 @@ public class AuthenticationPolicyServiceTest { } @Test - public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked() + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockEnabled() + throws RemoteException { + testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( + true /* enabled */); + } + + @Test + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockDisabled() throws RemoteException { + toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */); + testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( + false /* enabled */); + } + + private void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked( + boolean enabled) throws RemoteException { // Device is currently not locked and Keyguard is not showing when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); @@ -264,7 +284,11 @@ public class AuthenticationPolicyServiceTest { } waitForAuthCompletion(); - verifyLockDevice(PRIMARY_USER_ID); + if (enabled) { + verifyLockDevice(PRIMARY_USER_ID); + } else { + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID); + } } @Test @@ -300,8 +324,24 @@ public class AuthenticationPolicyServiceTest { } @Test - public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser() + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockEnabled() throws RemoteException { + testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( + true /* enabled */); + } + + @Test + @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK}) + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockDisabled() + throws RemoteException { + toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */); + testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( + false /* enabled */); + } + + private void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser( + boolean enabled) throws RemoteException { // Three failed primary auth attempts for (int i = 0; i < 3; i++) { mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); @@ -313,7 +353,11 @@ public class AuthenticationPolicyServiceTest { } waitForAuthCompletion(); - verifyLockDevice(PRIMARY_USER_ID); + if (enabled) { + verifyLockDevice(PRIMARY_USER_ID); + } else { + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID); + } } @Test @@ -366,10 +410,13 @@ public class AuthenticationPolicyServiceTest { REASON_UNKNOWN, true, userId).build(); } - private AuthenticationFailedInfo authFailedInfo(int userId) { return new AuthenticationFailedInfo.Builder(BiometricSourceType.FINGERPRINT, REASON_UNKNOWN, userId).build(); } + private void toggleAdaptiveAuthSettingsOverride(int userId, boolean disable) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, disable ? 1 : 0, userId); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 1df8e3deb84b..d2f8d14c5007 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -19,13 +19,9 @@ import static android.os.UserHandle.USER_ALL; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION; import static android.service.notification.Adjustment.TYPE_NEWS; -import static android.service.notification.Adjustment.TYPE_OTHER; import static android.service.notification.Adjustment.TYPE_PROMOTION; -import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; -import static android.service.notification.Flags.notificationClassification; import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS; -import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES; import static com.google.common.truth.Truth.assertThat; @@ -160,17 +156,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL); } - private void setDefaultAllowedAdjustmentKeyTypes(NotificationAssistants assistants) { - assistants.setAssistantAdjustmentKeyTypeState(TYPE_OTHER, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_SOCIAL_MEDIA, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false); - assistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, false); - - for (int type : DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES) { - assistants.setAssistantAdjustmentKeyTypeState(type, true); - } - } @Before public void setUp() throws Exception { @@ -181,9 +166,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { com.android.internal.R.string.config_defaultAssistantAccessComponent, mCn.flattenToString()); mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm)); - if (notificationClassification()) { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - } when(mNm.getBinderService()).thenReturn(mINm); mContext.ensureTestableResources(); @@ -727,7 +709,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true); assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() - .containsExactly(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION); + .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION)); } @Test @@ -748,7 +730,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { writeXmlAndReload(USER_ALL); assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() - .containsExactly(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION); + .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION)); } @Test @@ -765,168 +747,98 @@ public class NotificationAssistantsTest extends UiServiceTestCase { @Test @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION, android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI}) - public void testGetPackagesWithKeyTypeAdjustmentSettings() throws Exception { + public void testGetTypeAdjustmentDeniedPackages() throws Exception { String pkg = "my.package"; String pkg2 = "my.package.2"; - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()).isEmpty(); + assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg)).isTrue(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()) + mAssistants.setTypeAdjustmentForPackageState(pkg, true); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + mAssistants.setTypeAdjustmentForPackageState(pkg, false); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() .containsExactly(pkg); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()) + mAssistants.setTypeAdjustmentForPackageState(pkg2, true); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() .containsExactly(pkg); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_PROMOTION, false); - assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()) + mAssistants.setTypeAdjustmentForPackageState(pkg2, false); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() .containsExactly(pkg, pkg2); } @Test @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_usesGlobalDefault() { - String pkg = "my.package"; - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES); - } - - @Test - @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsAndDenies() { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - // Given that a package is set to have a type adjustment allowed, - String pkg = "my.package"; - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true); + public void testSetTypeAdjustmentForPackageState_allowsAndDenies() { + // Given that a package is allowed to have its type adjusted, + String allowedPackage = "allowed.package"; + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true); - // The newly set state is the combination of the global default and the newly set type. - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage)); // Set type adjustment disallowed for this package - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false); + mAssistants.setTypeAdjustmentForPackageState(allowedPackage, false); // Then the package is marked as denied - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).isEmpty(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactly(allowedPackage); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage)); // Set type adjustment allowed again - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true); + mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true); // Then the package is marked as allowed again - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - - // Set type adjustment promotions false, - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isFalse(); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty(); + assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage)); } @Test @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsMultiplePkgs() { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - // Given packages allowed to have their type adjusted to TYPE_NEWS, - String allowedPkg1 = "allowed.Pkg1"; - String allowedPkg2 = "allowed.Pkg2"; - String allowedPkg3 = "allowed.Pkg3"; - // Set type adjustment allowed for these packages - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg1, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true); - - // The newly set state is the combination of the global default and the newly set type. - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue(); - - // And when we deny some of them, - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_PROMOTION, - false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_PROMOTION, - false); - - // Then the rest of the original packages are still marked as allowed. - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).isEmpty(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList() - .containsExactly(TYPE_NEWS); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isFalse(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue(); + public void testSetAssistantAdjustmentKeyTypeStateForPackage_deniesMultiple() { + // Given packages not allowed to have their type adjusted, + String deniedPkg1 = "denied.Pkg1"; + String deniedPkg2 = "denied.Pkg2"; + String deniedPkg3 = "denied.Pkg3"; + // Set type adjustment disallowed for these packages + mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, false); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false); + + // Then the packages are marked as denied + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg2, deniedPkg3)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3)); + + // And when we re-allow one of them, + mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, true); + + // Then the rest of the original packages are still marked as denied. + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1)); + assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2)); + assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3)); } @Test @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) public void testSetAssistantAdjustmentKeyTypeStateForPackage_readWriteXml() throws Exception { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); mAssistants.loadDefaultsFromConfig(true); String deniedPkg1 = "denied.Pkg1"; String allowedPkg2 = "allowed.Pkg2"; - String allowedPkg3 = "allowed.Pkg3"; + String deniedPkg3 = "denied.Pkg3"; // Set type adjustment disallowed or allowed for these packages - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(deniedPkg1, TYPE_PROMOTION, false); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true); - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_SOCIAL_MEDIA, - true); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false); + mAssistants.setTypeAdjustmentForPackageState(allowedPkg2, true); + mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false); writeXmlAndReload(USER_ALL); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(deniedPkg1)).isEmpty(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList() - .containsExactly(TYPE_NEWS, TYPE_SOCIAL_MEDIA, TYPE_PROMOTION); - } - - @Test - @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) - public void testSetAssistantAdjustmentKeyTypeStateForPackage_noGlobalImpact() throws Exception { - setDefaultAllowedAdjustmentKeyTypes(mAssistants); - // When the global state is changed, - mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true); - - // The package state reflects the global state. - String pkg = "my.package"; - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue(); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION); - - // Once the package specific state is modified, - mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_SOCIAL_MEDIA, true); - - // The package specific state combines the global state with those modifications - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_SOCIAL_MEDIA)).isTrue(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA); - - // And further changes to the global state are ignored. - mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false); - assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue(); - assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList() - .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA); + assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList() + .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3)); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index fdb6a6802b7e..85c592084acf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -369,9 +369,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -387,6 +384,9 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper @@ -7662,7 +7662,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Set up notifications that will be adjusted final NotificationRecord r1 = spy(generateNotificationRecord( @@ -17512,7 +17512,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); Bundle signals = new Bundle(); signals.putInt(KEY_TYPE, TYPE_NEWS); @@ -17556,11 +17556,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_NEWS))) - .thenReturn(true); - // Blocking adjustments for a different type does nothing - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION))) - .thenReturn(false); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); Bundle signals = new Bundle(); signals.putInt(KEY_TYPE, TYPE_NEWS); @@ -17575,9 +17571,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); - // When we block adjustments for this package/type - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION))) - .thenReturn(false); + // When we block adjustments for this package + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(false); signals.putInt(KEY_TYPE, TYPE_PROMOTION); mBinderService.applyAdjustmentFromAssistant(null, adjustment); @@ -17907,7 +17902,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post a single notification final boolean hasOriginalSummary = false; @@ -17947,7 +17942,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post grouped notifications final String originalGroupName = "originalGroup"; @@ -17996,7 +17991,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post grouped notifications final String originalGroupName = "originalGroup"; @@ -18047,7 +18042,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); - when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); // Post a single notification final boolean hasOriginalSummary = false; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 6ef078b6da8a..96fddf13cdd0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -211,7 +211,9 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.io.Reader; +import java.io.StringWriter; import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -7406,6 +7408,43 @@ public class ZenModeHelperTest extends UiServiceTestCase { .isEqualTo(mZenModeHelper.getDefaultZenPolicy()); } + @Test + public void setAutomaticZenRuleState_logsOriginToZenLog() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr, + ORIGIN_APP, "adding", CUSTOM_PKG_UID); + ZenLog.clear(); + + // User enables manually from QS: + mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, + new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_USER_IN_SYSTEMUI, + 123456); + + assertThat(getZenLog()).contains( + "config: setAzrState: " + ruleId + " (ORIGIN_USER_IN_SYSTEMUI) from uid " + 1234); + } + + @Test + public void setAutomaticZenRuleStateFromConditionProvider_logsOriginToZenLog() { + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond/cond")) + .setOwner(new ComponentName(CUSTOM_PKG_NAME, "SomeConditionProvider")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr, + ORIGIN_APP, "adding", CUSTOM_PKG_UID); + ZenLog.clear(); + + // App enables rule through CPS + mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, + Uri.parse("cond/cond"), new Condition(azr.getConditionId(), "", STATE_TRUE), + ORIGIN_APP, CUSTOM_PKG_UID); + + assertThat(getZenLog()).contains( + "config: setAzrStateFromCps: cond/cond (ORIGIN_APP) from uid " + CUSTOM_PKG_UID); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); @@ -7530,6 +7569,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW); } + private static String getZenLog() { + StringWriter zenLogWriter = new StringWriter(); + ZenLog.dump(new PrintWriter(zenLogWriter), ""); + return zenLogWriter.toString(); + } + private static void withoutWtfCrash(Runnable test) { Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> { }); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index 22e411ea1500..3f3b24a21d22 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -202,7 +202,7 @@ public class DisplayAreaPolicyBuilderTest { } @Test - public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() { + public void testBuilder_defaultPolicy_hasMagnificationFeature() { final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( resourcesWithProvider("")); final DisplayAreaPolicyBuilder.Result defaultPolicy = @@ -210,28 +210,16 @@ public class DisplayAreaPolicyBuilderTest { mRoot, mImeContainer); final List<Feature> features = defaultPolicy.getFeatures(); boolean hasWindowedMagnificationFeature = false; - for (Feature feature : features) { - hasWindowedMagnificationFeature |= feature.getId() == FEATURE_WINDOWED_MAGNIFICATION; - } - - assertThat(hasWindowedMagnificationFeature).isTrue(); - } - - @Test - public void testBuilder_defaultPolicy_hasFullscreenMagnificationFeature() { - final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources( - resourcesWithProvider("")); - final DisplayAreaPolicyBuilder.Result defaultPolicy = - (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent, - mRoot, mImeContainer); - final List<Feature> features = defaultPolicy.getFeatures(); boolean hasFullscreenMagnificationFeature = false; for (Feature feature : features) { + hasWindowedMagnificationFeature |= feature.getId() == FEATURE_WINDOWED_MAGNIFICATION; hasFullscreenMagnificationFeature |= feature.getId() == FEATURE_FULLSCREEN_MAGNIFICATION; } - assertThat(hasFullscreenMagnificationFeature).isTrue(); + assertThat(hasWindowedMagnificationFeature).isTrue(); + assertThat(hasFullscreenMagnificationFeature).isEqualTo( + DisplayAreaPolicy.USE_DISPLAY_AREA_FOR_FULLSCREEN_MAGNIFICATION); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index f509706d1692..523b7235140a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -36,6 +36,7 @@ import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTEN import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -337,12 +338,13 @@ public class DragDropControllerTests extends WindowTestsBase { // Verify the drop event is only sent for the global intercept window assertTrue(nonLocalWindowDragEvents.isEmpty()); - assertTrue(last(localWindowDragEvents).getAction() != ACTION_DROP); - assertTrue(last(globalInterceptWindowDragEvents).getAction() == ACTION_DROP); + assertNotEquals(ACTION_DROP, localWindowDragEvents.getLast().getAction()); + assertEquals(ACTION_DROP, + globalInterceptWindowDragEvents.getLast().getAction()); // Verify that item extras were not sent with the drop event - assertNull(last(localWindowDragEvents).getClipData()); - assertFalse(last(globalInterceptWindowDragEvents).getClipData() + assertNull(localWindowDragEvents.getLast().getClipData()); + assertFalse(globalInterceptWindowDragEvents.getLast().getClipData() .willParcelWithActivityInfo()); }); } @@ -384,7 +386,7 @@ public class DragDropControllerTests extends WindowTestsBase { } @Test - public void testDragEventCoordinates() { + public void testDragEventCoordinatesOverlappingWindows() { int dragStartX = mWindow.getBounds().centerX(); int dragStartY = mWindow.getBounds().centerY(); int startOffsetPx = 10; @@ -429,7 +431,7 @@ public class DragDropControllerTests extends WindowTestsBase { // Verify only window2 received the DROP event and coords are sent as-is. assertEquals(1, dragEvents.size()); assertEquals(2, dragEvents2.size()); - final DragEvent dropEvent = last(dragEvents2); + final DragEvent dropEvent = dragEvents2.getLast(); assertEquals(ACTION_DROP, dropEvent.getAction()); assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */); assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */); @@ -437,10 +439,10 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.reportDropResult(iwindow2, true); // Verify both windows received ACTION_DRAG_ENDED event. - assertEquals(ACTION_DRAG_ENDED, last(dragEvents).getAction()); - assertEquals(window2.getDisplayId(), last(dragEvents).getDisplayId()); - assertEquals(ACTION_DRAG_ENDED, last(dragEvents2).getAction()); - assertEquals(window2.getDisplayId(), last(dragEvents2).getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction()); + assertEquals(window2.getDisplayId(), dragEvents.getLast().getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents2.getLast().getAction()); + assertEquals(window2.getDisplayId(), dragEvents2.getLast().getDisplayId()); } finally { mTarget.continueDragStateClose(); } @@ -493,7 +495,7 @@ public class DragDropControllerTests extends WindowTestsBase { // Verify only window2 received the DROP event and coords are sent as-is assertEquals(1, dragEvents.size()); assertEquals(2, dragEvents2.size()); - final DragEvent dropEvent = last(dragEvents2); + final DragEvent dropEvent = dragEvents2.getLast(); assertEquals(ACTION_DROP, dropEvent.getAction()); assertEquals(dropCoordsPx, dropEvent.getX(), 0.0 /* delta */); assertEquals(dropCoordsPx, dropEvent.getY(), 0.0 /* delta */); @@ -501,10 +503,12 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.reportDropResult(iwindow2, true); // Verify both windows received ACTION_DRAG_ENDED event. - assertEquals(ACTION_DRAG_ENDED, last(dragEvents).getAction()); - assertEquals(testDisplay.getDisplayId(), last(dragEvents).getDisplayId()); - assertEquals(ACTION_DRAG_ENDED, last(dragEvents2).getAction()); - assertEquals(testDisplay.getDisplayId(), last(dragEvents2).getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction()); + assertEquals(testDisplay.getDisplayId(), + dragEvents.getLast().getDisplayId()); + assertEquals(ACTION_DRAG_ENDED, dragEvents2.getLast().getAction()); + assertEquals(testDisplay.getDisplayId(), + dragEvents2.getLast().getDisplayId()); } finally { mTarget.continueDragStateClose(); } @@ -561,8 +565,23 @@ public class DragDropControllerTests extends WindowTestsBase { }); } - private DragEvent last(ArrayList<DragEvent> list) { - return list.get(list.size() - 1); + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND) + public void testDragCancelledOnTopologyChange() { + // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events + // immediately after dispatching, which is a problem when using mockito arguments captor + // because it returns and modifies the same drag event. + TestIWindow iwindow = (TestIWindow) mWindow.mClient; + final ArrayList<DragEvent> dragEvents = new ArrayList<>(); + iwindow.setDragEventJournal(dragEvents); + + startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, + ClipData.newPlainText("label", "text"), (surface) -> { + // Simulate display topology change to trigger drag-and-drop cancellation. + mTarget.handleDisplayTopologyChange(null /* displayTopology */); + assertEquals(2, dragEvents.size()); + assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction()); + }); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 25b9f4b8035b..8a7e7434e604 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -70,8 +70,6 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserManager; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; @@ -82,11 +80,9 @@ import android.window.TaskSnapshot; import androidx.test.filters.MediumTest; -import com.android.launcher3.Flags; import com.android.server.wm.RecentTasks.Callbacks; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -717,18 +713,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) - public void testVisibleTasks_excludedFromRecents() { - testVisibleTasks_excludedFromRecents_internal(); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_withRefactorFlag() { - testVisibleTasks_excludedFromRecents_internal(); - } - - private void testVisibleTasks_excludedFromRecents_internal() { mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); Task invisibleExcludedTask = createTaskBuilder(".ExcludedTask1") @@ -766,19 +751,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @Ignore("b/342627272") - @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) - public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() { - testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() { - testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); - } - - private void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal() { mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); Task invisibleExcludedTask = createTaskBuilder(".ExcludedTask1") @@ -816,18 +789,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) - public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() { - testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() { - testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); - } - - private void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal() { // Create some set of tasks, some of which are visible and some are not Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask") .setParentTask(mTaskContainer.getRootHomeTask()) diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index a95093d7e113..59335d3bb135 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -264,6 +264,7 @@ public class SystemServicesTestRule implements TestRule { spyOn(dmg); doNothing().when(dmg).registerDisplayListener( any(), any(Executor.class), anyLong(), anyString()); + doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString()); } private void setUpLocalServices() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index c876663dd749..8a068cc7837a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.os.Build.HW_TIMEOUT_MULTIPLIER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -41,10 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Color; -import android.graphics.PixelFormat; -import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; -import android.media.ImageReader; import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; @@ -57,13 +51,14 @@ import android.widget.LinearLayout; import androidx.test.filters.MediumTest; import com.android.server.wm.utils.CommonUtils; +import com.android.server.wm.utils.VirtualDisplayTestRule; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.ArrayList; -import java.util.Arrays; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -76,9 +71,9 @@ import java.util.function.Predicate; @MediumTest public class TaskStackChangedListenerTest { + @Rule + public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule(); private ITaskStackListener mTaskStackListener; - private VirtualDisplay mVirtualDisplay; - private ImageReader mImageReader; private final ArrayList<Activity> mStartedActivities = new ArrayList<>(); private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER; @@ -94,10 +89,6 @@ public class TaskStackChangedListenerTest { if (mTaskStackListener != null) { ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener); } - if (mVirtualDisplay != null) { - mVirtualDisplay.release(); - mImageReader.close(); - } // Finish from bottom to top. final int size = mStartedActivities.size(); for (int i = 0; i < size; i++) { @@ -116,21 +107,9 @@ public class TaskStackChangedListenerTest { private VirtualDisplay createVirtualDisplay() { final int width = 800; final int height = 600; - final int density = 160; - final DisplayManager displayManager = getInstrumentation().getContext().getSystemService( - DisplayManager.class); - mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, - 2 /* maxImages */); - final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - | VIRTUAL_DISPLAY_FLAG_PUBLIC; final String name = getClass().getSimpleName() + "_VirtualDisplay"; - mVirtualDisplay = displayManager.createVirtualDisplay(name, width, height, density, - mImageReader.getSurface(), flags); - mVirtualDisplay.setSurface(mImageReader.getSurface()); - assertNotNull("display must be registered", - Arrays.stream(displayManager.getDisplays()).filter( - d -> d.getName().equals(name)).findAny()); - return mVirtualDisplay; + return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width, + height); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 38d3d2fec942..724d7e7c111c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -92,6 +92,7 @@ import android.util.Xml; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; +import android.view.WindowInsetsController; import android.window.TaskFragmentOrganizer; import androidx.test.filters.MediumTest; @@ -2109,6 +2110,43 @@ public class TaskTests extends WindowTestsBase { assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor()); } + @Test + public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesTransparent() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(task); + final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(); + td.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); + activity.setTaskDescription(td); + + assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + + activity.setOccludesParent(false); + + assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + } + + @Test + public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesOpaque() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(task); + activity.setOccludesParent(false); + + final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(); + td.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); + activity.setTaskDescription(td); + + assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + + activity.setOccludesParent(true); + + assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/VirtualDisplayTestRule.java b/services/tests/wmtests/src/com/android/server/wm/utils/VirtualDisplayTestRule.java new file mode 100644 index 000000000000..e92e6846e161 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/VirtualDisplayTestRule.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertNotNull; + +import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.media.ImageReader; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Provides wrapper test rule for creating and managing the cleanup for VirtualDisplay */ +public class VirtualDisplayTestRule implements TestRule { + private static final int DISPLAY_DENSITY = 160; + + private final List<VirtualDisplay> mVirtualDisplays = new ArrayList<>(); + private final List<ImageReader> mImageReaders = new ArrayList<>(); + private final DisplayManager mDisplayManager; + + public VirtualDisplayTestRule() { + mDisplayManager = getInstrumentation().getTargetContext().getSystemService( + DisplayManager.class); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + tearDown(); + } + } + }; + } + + private void tearDown() { + mVirtualDisplays.forEach(VirtualDisplay::release); + mImageReaders.forEach(ImageReader::close); + } + + /** + * The virtual display in WindowTestsBase#createMockSimulatedDisplay is only attached to WM + * DisplayWindowSettingsProvider. DisplayManager is not aware of mock simulated display and + * therefore couldn't be used for actual Display-related testing (e.g. display listeners). + * This method creates real VirtualDisplay through DisplayManager. + */ + public VirtualDisplay createDisplayManagerAttachedVirtualDisplay(String name, int width, + int height) { + final ImageReader imageReader = ImageReader.newInstance(width, height, + PixelFormat.RGBA_8888, 2 /* maxImages */); + mImageReaders.add(imageReader); + final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + | VIRTUAL_DISPLAY_FLAG_PUBLIC; + final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(name, width, + height, DISPLAY_DENSITY, imageReader.getSurface(), flags); + mVirtualDisplays.add(virtualDisplay); + assertNotNull("display must be registered", Arrays.stream( + mDisplayManager.getDisplays()).filter(d -> d.getName().equals(name)).findAny()); + return virtualDisplay; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java index 819e73df955b..6dda7ea3eb59 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java @@ -48,6 +48,13 @@ public abstract class UsbACTerminal extends UsbACInterface { return mAssocTerminal; } + public boolean isInputTerminal() { + return mTerminalType == UsbTerminalTypes.TERMINAL_IN_MIC + || mTerminalType == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET + || mTerminalType == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED + || mTerminalType == UsbTerminalTypes.TERMINAL_EXTERN_LINE; + } + @Override public int parseRawDescriptors(ByteStream stream) { mTerminalID = stream.getByte(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index ba178845a536..bfa4ecd71f5a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -524,27 +524,21 @@ public final class UsbDescriptorParser { * @hide */ public boolean hasMic() { - boolean hasMic = false; - ArrayList<UsbDescriptor> acDescriptors = getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, UsbACInterface.AUDIO_AUDIOCONTROL); for (UsbDescriptor descriptor : acDescriptors) { if (descriptor instanceof UsbACTerminal) { UsbACTerminal inDescr = (UsbACTerminal) descriptor; - if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC - || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET - || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED - || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) { - hasMic = true; - break; + if (inDescr.isInputTerminal()) { + return true; } } else { Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength() + " t:0x" + Integer.toHexString(descriptor.getType())); } } - return hasMic; + return false; } /** @@ -913,18 +907,20 @@ public final class UsbDescriptorParser { float probability = 0.0f; - // Look for a microphone - boolean hasMic = hasMic(); - // Look for a "speaker" boolean hasSpeaker = hasSpeaker(); - if (hasMic && hasSpeaker) { - probability += 0.75f; - } - - if (hasMic && hasHIDInterface()) { - probability += 0.25f; + if (hasMic()) { + if (hasSpeaker) { + probability += 0.75f; + } + if (hasHIDInterface()) { + probability += 0.25f; + } + if (getMaximumInputChannelCount() > 1) { + // A headset is more likely to only support mono capture. + probability -= 0.25f; + } } return probability; @@ -935,9 +931,11 @@ public final class UsbDescriptorParser { * headset. The probability range is between 0.0f (definitely NOT a headset) and * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient * to count on the peripheral being a headset. + * To align with the output device type, only treat the device as input headset if it is + * an output headset. */ public boolean isInputHeadset() { - return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; + return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER && isOutputHeadset(); } // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here. @@ -952,6 +950,32 @@ public final class UsbDescriptorParser { return maxChannelCount; } + private int getMaximumInputChannelCount() { + int maxChannelCount = 0; + ArrayList<UsbDescriptor> acDescriptors = + getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + for (UsbDescriptor descriptor : acDescriptors) { + if (!(descriptor instanceof UsbACTerminal)) { + continue; + } + UsbACTerminal inDescr = (UsbACTerminal) descriptor; + if (!inDescr.isInputTerminal()) { + continue; + } + // For an input terminal, it should at lease has 1 channel. + // Comparing the max channel count with 1 here in case the USB device doesn't report + // audio channel cluster. + maxChannelCount = Math.max(maxChannelCount, 1); + if (!(descriptor instanceof UsbAudioChannelCluster)) { + continue; + } + maxChannelCount = Math.max(maxChannelCount, + ((UsbAudioChannelCluster) descriptor).getChannelCount()); + } + return maxChannelCount; + } + /** * @hide */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 24fb8c5da2d7..da4165553e57 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -180,7 +180,7 @@ import java.util.stream.IntStream; * permission-protected. Your application cannot access the protected * information unless it has the appropriate permissions declared in * its manifest file. Where permissions apply, they are noted in the - * the methods through which you access the protected information. + * methods through which you access the protected information. * * <p>TelephonyManager is intended for use on devices that implement * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices @@ -633,11 +633,14 @@ public class TelephonyManager { } /** - * Returns the multi SIM variant - * Returns DSDS for Dual SIM Dual Standby - * Returns DSDA for Dual SIM Dual Active - * Returns TSTS for Triple SIM Triple Standby - * Returns UNKNOWN for others + * Returns the multi SIM variant. + * + * <ul> + * <li>Returns DSDS for Dual SIM Dual Standby.</li> + * <li>Returns DSDA for Dual SIM Dual Active.</li> + * <li>Returns TSTS for Triple SIM Triple Standby.</li> + * <li>Returns UNKNOWN for others.</li> + * </ul> */ /** {@hide} */ @UnsupportedAppUsage @@ -657,10 +660,14 @@ public class TelephonyManager { /** * Returns the number of phones available. - * Returns 0 if none of voice, sms, data is not supported - * Returns 1 for Single standby mode (Single SIM functionality). - * Returns 2 for Dual standby mode (Dual SIM functionality). - * Returns 3 for Tri standby mode (Tri SIM functionality). + * + * <ul> + * <li>Returns 0 if none of voice, sms, data is supported.</li> + * <li>Returns 1 for Single standby mode (Single SIM functionality).</li> + * <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li> + * <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li> + * </ul> + * * @deprecated Use {@link #getActiveModemCount} instead. */ @Deprecated @@ -671,10 +678,12 @@ public class TelephonyManager { /** * Returns the number of logical modems currently configured to be activated. * - * Returns 0 if none of voice, sms, data is not supported - * Returns 1 for Single standby mode (Single SIM functionality). - * Returns 2 for Dual standby mode (Dual SIM functionality). - * Returns 3 for Tri standby mode (Tri SIM functionality). + * <ul> + * <li>Returns 0 if none of voice, sms, data is supported.</li> + * <li>Returns 1 for Single standby mode (Single SIM functionality).</li> + * <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li> + * <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li> + * </ul> */ public int getActiveModemCount() { int modemCount = 1; diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 8c04f647fb2f..e0532633d40b 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -736,30 +736,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + ALT + '-' -> Magnification Zoom Out", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_MINUS - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_OUT, - intArrayOf(KeyEvent.KEYCODE_MINUS), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + '=' -> Magnification Zoom In", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_EQUALS - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_ZOOM_IN, - intArrayOf(KeyEvent.KEYCODE_EQUALS), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + ALT + M -> Toggle Magnification", intArrayOf( KeyEvent.KEYCODE_META_LEFT, @@ -784,54 +760,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + ALT + 'Down' -> Magnification Pan Down", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_DOWN - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN, - intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + 'Up' -> Magnification Pan Up", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_UP - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP, - intArrayOf(KeyEvent.KEYCODE_DPAD_UP), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + 'Left' -> Magnification Pan Left", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_LEFT, - intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "META + ALT + 'Right' -> Magnification Pan Right", - intArrayOf( - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_RIGHT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT, - intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT), - KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + ALT + 'V' -> Toggle Voice Access", intArrayOf( KeyEvent.KEYCODE_META_LEFT, diff --git a/tests/NetworkSecurityConfigTest/Android.bp b/tests/NetworkSecurityConfigTest/Android.bp index 4c48eaa4622e..6a68f2bb7ff9 100644 --- a/tests/NetworkSecurityConfigTest/Android.bp +++ b/tests/NetworkSecurityConfigTest/Android.bp @@ -11,11 +11,12 @@ android_test { name: "NetworkSecurityConfigTests", certificate: "platform", libs: [ - "android.test.runner.stubs.system", "android.test.base.stubs.system", + "android.test.runner.stubs.system", ], static_libs: ["junit"], // Include all test java files. srcs: ["src/**/*.java"], platform_apis: true, + test_suites: ["general-tests"], } diff --git a/tests/NetworkSecurityConfigTest/TEST_MAPPING b/tests/NetworkSecurityConfigTest/TEST_MAPPING new file mode 100644 index 000000000000..d1b9aa1e3b53 --- /dev/null +++ b/tests/NetworkSecurityConfigTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "NetworkSecurityConfigTests" + } + ] +} diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index a5962292b5b0..82ad9fa05145 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -2778,7 +2778,7 @@ bool ManifestExtractor::Extract(android::IDiagnostics* diag) { auto file_path = it->Next()->GetSource().path.c_str(); const char* last_slash = - android::util::ValidLibraryPathLastSlash(file_path, has_renderscript_bitcode, false); + android::util::ValidLibraryPathLastSlash(file_path, has_renderscript_bitcode); if (last_slash) { architectures_from_apk.insert(std::string(file_path + APK_LIB_LEN, last_slash)); } |