Merge "Fix NPE in StackScrollAlgorithm.isCyclingIn/Out" into main
diff --git a/Android.bp b/Android.bp
index d6b303f..af312bf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -425,6 +425,7 @@
"sounddose-aidl-java",
"modules-utils-expresslog",
"perfetto_trace_javastream_protos_jarjar",
+ "libaconfig_java_proto_nano",
],
}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 13d6ae5..6cfd2e0 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -44,8 +44,8 @@
removed_api_file: ":non-updatable-removed.txt",
},
last_released: {
- api_file: ":android-non-updatable.api.public.latest",
- removed_api_file: ":android-non-updatable-removed.api.public.latest",
+ api_file: ":android-non-updatable.api.combined.public.latest",
+ removed_api_file: ":android-non-updatable-removed.api.combined.public.latest",
baseline_file: ":android-non-updatable-incompatibilities.api.public.latest",
},
api_lint: {
@@ -124,8 +124,8 @@
removed_api_file: ":non-updatable-system-removed.txt",
},
last_released: {
- api_file: ":android-non-updatable.api.system.latest",
- removed_api_file: ":android-non-updatable-removed.api.system.latest",
+ api_file: ":android-non-updatable.api.combined.system.latest",
+ removed_api_file: ":android-non-updatable-removed.api.combined.system.latest",
baseline_file: ":android-non-updatable-incompatibilities.api.system.latest",
},
api_lint: {
@@ -263,8 +263,8 @@
removed_api_file: ":non-updatable-module-lib-removed.txt",
},
last_released: {
- api_file: ":android-non-updatable.api.module-lib.latest",
- removed_api_file: ":android-non-updatable-removed.api.module-lib.latest",
+ api_file: ":android-non-updatable.api.combined.module-lib.latest",
+ removed_api_file: ":android-non-updatable-removed.api.combined.module-lib.latest",
baseline_file: ":android-non-updatable-incompatibilities.api.module-lib.latest",
},
api_lint: {
diff --git a/core/api/current.txt b/core/api/current.txt
index 16de8fe7..6c5a5c9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26611,7 +26611,7 @@
public abstract static class MediaController.Callback {
ctor public MediaController.Callback();
- method public void onAudioInfoChanged(android.media.session.MediaController.PlaybackInfo);
+ method public void onAudioInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
method public void onExtrasChanged(@Nullable android.os.Bundle);
method public void onMetadataChanged(@Nullable android.media.MediaMetadata);
method public void onPlaybackStateChanged(@Nullable android.media.session.PlaybackState);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9ea55f5..c6a1546 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -103,7 +103,8 @@
@IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
- MODE_BACKGROUND_ACTIVITY_START_DENIED})
+ MODE_BACKGROUND_ACTIVITY_START_DENIED,
+ MODE_BACKGROUND_ACTIVITY_START_COMPAT})
public @interface BackgroundActivityStartMode {}
/**
* No explicit value chosen. The system will decide whether to grant privileges.
@@ -117,6 +118,13 @@
* Deny the {@link PendingIntent} to use the background activity start privileges.
*/
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
+ /**
+ * Special behavior for compatibility.
+ * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED}
+ *
+ * @hide
+ */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_COMPAT = -1;
/**
* The package name that created the options.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d4812dd..76c1ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull;
+import static android.app.Flags.skipBgMemTrimOnFgApp;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
@@ -7078,6 +7079,11 @@
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
try {
+ if (skipBgMemTrimOnFgApp()
+ && mLastProcessState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
+ return;
+ }
if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
PropertyInvalidatedCache.onTrimMemory();
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 397477d..0e8e2e3 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -18,6 +18,7 @@
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
@@ -54,7 +55,7 @@
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
- private @Nullable Boolean mPendingIntentBalAllowed = null;
+ private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
private boolean mPendingIntentBalAllowedByPermission = false;
ComponentOptions() {
@@ -65,12 +66,9 @@
// results they want, which is their loss.
opts.setDefusable(true);
- boolean pendingIntentBalAllowedIsSetExplicitly =
- opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
- if (pendingIntentBalAllowedIsSetExplicitly) {
- mPendingIntentBalAllowed =
- opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
- }
+ mPendingIntentBalAllowed =
+ opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(
KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
@@ -85,7 +83,8 @@
* @hide
*/
@Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
- mPendingIntentBalAllowed = allowed;
+ mPendingIntentBalAllowed = allowed ? MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ : MODE_BACKGROUND_ACTIVITY_START_DENIED;
}
/**
@@ -98,11 +97,8 @@
* @hide
*/
@Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
- if (mPendingIntentBalAllowed == null) {
- // cannot return null, so return the value used up to API level 33 for compatibility
- return true;
- }
- return mPendingIntentBalAllowed;
+ // cannot return all detail, so return the value used up to API level 33 for compatibility
+ return mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_DENIED;
}
/**
@@ -119,16 +115,15 @@
@BackgroundActivityStartMode int state) {
switch (state) {
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
- mPendingIntentBalAllowed = null;
- break;
- case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
- mPendingIntentBalAllowed = true;
- break;
case MODE_BACKGROUND_ACTIVITY_START_DENIED:
- mPendingIntentBalAllowed = false;
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ mPendingIntentBalAllowed = state;
break;
default:
- throw new IllegalArgumentException(state + " is not valid");
+ // Assume that future values are some variant of allowing the start.
+ mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ break;
}
return this;
}
@@ -141,13 +136,7 @@
* @see #setPendingIntentBackgroundActivityStartMode(int)
*/
public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
- if (mPendingIntentBalAllowed == null) {
- return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
- } else if (mPendingIntentBalAllowed) {
- return MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return MODE_BACKGROUND_ACTIVITY_START_DENIED;
- }
+ return mPendingIntentBalAllowed;
}
/**
@@ -170,8 +159,8 @@
/** @hide */
public Bundle toBundle() {
Bundle b = new Bundle();
- if (mPendingIntentBalAllowed != null) {
- b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+ if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
}
if (mPendingIntentBalAllowedByPermission) {
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index bb24fd1..fa646a7 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -70,3 +70,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "backstage_power"
+ name: "skip_bg_mem_trim_on_fg_app"
+ description: "Skip background memory trim event on foreground processes."
+ is_fixed_read_only: true
+ bug: "308927629"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 4154e66..ad1ae98 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -115,6 +115,16 @@
}
flag {
+ name: "hsum_unlock_notification_fix"
+ namespace: "enterprise"
+ description: "Using the right userId when starting the work profile unlock flow "
+ bug: "327350831"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "dumpsys_policy_engine_migration_enabled"
namespace: "enterprise"
description: "Update DumpSys to include information about migrated APIs in DPE"
@@ -318,6 +328,13 @@
}
flag {
+ name: "backup_connected_apps_settings"
+ namespace: "enterprise"
+ description: "backup and restore connected work and personal apps user settings across devices"
+ bug: "175067666"
+}
+
+flag {
name: "headless_single_user_compatibility_fix"
namespace: "enterprise"
description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds"
@@ -336,3 +353,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "onboarding_consentless_bugreports"
+ namespace: "enterprise"
+ description: "Allow subsequent bugreports to skip user consent within a time frame"
+ bug: "340439309"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index b074e8b..2e252c1 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -60,6 +60,10 @@
* {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
*/
public class IntentSender implements Parcelable {
+ private static final Bundle SEND_INTENT_DEFAULT_OPTIONS =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle();
+
@UnsupportedAppUsage
private final IIntentSender mTarget;
IBinder mWhitelistToken;
@@ -161,7 +165,8 @@
*/
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler) throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
+ sendIntent(context, code, intent, onFinished, handler, null,
+ SEND_INTENT_DEFAULT_OPTIONS);
}
/**
@@ -194,7 +199,7 @@
OnFinished onFinished, Handler handler, String requiredPermission)
throws SendIntentException {
sendIntent(context, code, intent, onFinished, handler, requiredPermission,
- null /* options */);
+ SEND_INTENT_DEFAULT_OPTIONS);
}
/**
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 8220313..57ee622 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -326,6 +326,7 @@
* @return whether the specified user is a profile.
*/
@FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE)
+ @SuppressWarnings("UserHandleName")
public boolean isProfile(@NonNull UserHandle userHandle) {
// Note that this is not a security check, but rather a check for correct use.
// The actual security check is performed by UserManager.
@@ -343,6 +344,7 @@
* @return whether the specified user is a managed profile.
*/
@FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE)
+ @SuppressWarnings("UserHandleName")
public boolean isManagedProfile(@NonNull UserHandle userHandle) {
// Note that this is not a security check, but rather a check for correct use.
// The actual security check is performed by UserManager.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 885f4c5..982224b 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -806,7 +806,7 @@
*
* <aside class="note"><b>Note:</b> If the app targets
* {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- * or after, The width measurement reflects the window size without excluding insets.
+ * or after, the width measurement reflects the window size without excluding insets.
* Otherwise, the measurement excludes window insets even when the app is displayed edge to edge
* using {@link android.view.Window#setDecorFitsSystemWindows(boolean)
* Window#setDecorFitsSystemWindows(boolean)}.</aside>
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e2b409f..7f3c49d 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1571,8 +1571,7 @@
}
// Allow RAW formats, even when not advertised.
- if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
- || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+ if (isRawFormat(inputFormat)) {
return true;
}
@@ -1642,6 +1641,11 @@
}
}
+ // Allow RAW formats, even when not advertised.
+ if (Flags.multiResRawReprocessing() && isRawFormat(inputFormat)) {
+ return;
+ }
+
if (validFormat == false) {
throw new IllegalArgumentException("multi-resolution input format " +
inputFormat + " is not valid");
@@ -2584,6 +2588,11 @@
return mCharacteristics;
}
+ private boolean isRawFormat(int format) {
+ return (format == ImageFormat.RAW_PRIVATE || format == ImageFormat.RAW10
+ || format == ImageFormat.RAW12 || format == ImageFormat.RAW_SENSOR);
+ }
+
/**
* Listener for binder death.
*
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 960e84d..a818df5 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -252,7 +252,8 @@
params.getMode(),
params.getFlags(),
dsListener,
- isScreenshotRequested);
+ isScreenshotRequested,
+ /* skipUserConsent = */ false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (FileNotFoundException e) {
@@ -313,6 +314,7 @@
bugreportFd.getFileDescriptor(),
bugreportFile,
/* keepBugreportOnRetrieval = */ false,
+ /* skipUserConsent = */ false,
dsListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2f0d634..80d3566 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -368,17 +368,18 @@
public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
/**
- * Specifies if a user is disallowed from being granted admin privileges.
+ * Restricts a user's ability to possess or grant admin privileges.
*
- * <p>This restriction limits ability of other admin users to grant admin
- * privileges to selected user.
+ * <p>When set to <code>true</code>, this prevents the user from:
+ * <ul>
+ * <li>Becoming an admin</li>
+ * <li>Giving other users admin privileges</li>
+ * </ul>
*
- * <p>This restriction has no effect in a mode that does not allow multiple admins.
+ * <p>This restriction is only effective in environments where multiple admins are allowed.
*
- * <p>The default value is <code>false</code>.
+ * <p>Key for user restrictions. Type: Boolean. Default: <code>false</code>.
*
- * <p>Key for user restrictions.
- * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4f5b67c..3738c26 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17032,6 +17032,28 @@
*/
public static final String ENABLE_BACK_ANIMATION = "enable_back_animation";
+ /**
+ * An allow list of packages for which the user has granted the permission to communicate
+ * across profiles.
+ *
+ * @hide
+ */
+ @Readable
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_BACKUP_CONNECTED_APPS_SETTINGS)
+ public static final String CONNECTED_APPS_ALLOWED_PACKAGES =
+ "connected_apps_allowed_packages";
+
+ /**
+ * A block list of packages for which the user has denied the permission to communicate
+ * across profiles.
+ *
+ * @hide
+ */
+ @Readable
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_BACKUP_CONNECTED_APPS_SETTINGS)
+ public static final String CONNECTED_APPS_DISALLOWED_PACKAGES =
+ "connected_apps_disallowed_packages";
+
/** @hide */ public static String zenModeToString(int mode) {
if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS";
if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS";
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 38ab590..71066ac 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -433,7 +433,8 @@
mTrackingConfirmKey = event.getKeyCode();
}
case KeyEvent.ACTION_UP -> {
- if (mTrackingConfirmKey != event.getKeyCode()) {
+ if (mTrackingConfirmKey == null
+ || mTrackingConfirmKey != event.getKeyCode()) {
return true;
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7b..a78a417 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -310,6 +310,7 @@
* @see Layout#getUseBoundsForWidth()
* @see Layout.Builder#setUseBoundsForWidth(boolean)
*/
+ @SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter.
@NonNull
@FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9e..95460a3 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -447,6 +447,7 @@
* @see Layout#getUseBoundsForWidth()
* @see Layout.Builder#setUseBoundsForWidth(boolean)
*/
+ @SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter.
@NonNull
@FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4475418..6464239 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -916,6 +916,12 @@
* {@code getWindowManager()} or {@code getSystemService(Context.WINDOW_SERVICE)}), the
* size of the current app window is returned. As a result, in multi-window mode, the
* returned size can be smaller than the size of the device screen.
+ * The returned window size can vary depending on API level:
+ * <ul>
+ * <li>API level 35 and above, the window size will be returned.
+ * <li>API level 34 and below, the window size minus system decoration areas and
+ * display cutout is returned.
+ * </ul>
* <li>If size is requested from a non-activity context (for example, the application
* context, where the WindowManager is accessed by
* {@code getApplicationContext().getSystemService(Context.WINDOW_SERVICE)}), the
@@ -924,9 +930,10 @@
* <li>API level 29 and below — The size of the entire display (based on
* current rotation) minus system decoration areas is returned.
* <li>API level 30 and above — The size of the top running activity in the
- * current process is returned. If the current process has no running
- * activities, the size of the device default display, including system
- * decoration areas, is returned.
+ * current process is returned, system decoration areas exclusion follows the
+ * behavior defined above, based on the caller's API level. If the current
+ * process has no running activities, the size of the device default display,
+ * including system decoration areas, is returned.
* </ul>
* </ul>
*
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d7f2b01..fd10a1f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -303,7 +303,7 @@
}
/** Not running an animation. */
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public static final int ANIMATION_TYPE_NONE = -1;
/** Running animation will show insets */
@@ -317,7 +317,7 @@
public static final int ANIMATION_TYPE_USER = 2;
/** Running animation will resize insets */
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public static final int ANIMATION_TYPE_RESIZE = 3;
@Retention(RetentionPolicy.SOURCE)
@@ -1714,7 +1714,7 @@
mImeSourceConsumer.onWindowFocusLost();
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public @AnimationType int getAnimationType(@InsetsType int type) {
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index fdb2a6e..6c670f5 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
@@ -31,6 +32,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -179,10 +181,11 @@
mController.notifyVisibilityChanged();
}
- // If we have a new leash, make sure visibility is up-to-date, even though we
- // didn't want to run an animation above.
- if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
- applyRequestedVisibilityToControl();
+ // If there is no animation controlling the leash, make sure the visibility and the
+ // position is up-to-date.
+ final int animType = mController.getAnimationType(mType);
+ if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) {
+ applyRequestedVisibilityAndPositionToControl();
}
// Remove the surface that owned by last control when it lost.
@@ -371,21 +374,27 @@
if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
}
- private void applyRequestedVisibilityToControl() {
- if (mSourceControl == null || mSourceControl.getLeash() == null) {
+ private void applyRequestedVisibilityAndPositionToControl() {
+ if (mSourceControl == null) {
+ return;
+ }
+ final SurfaceControl leash = mSourceControl.getLeash();
+ if (leash == null) {
return;
}
final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
+ final Point surfacePosition = mSourceControl.getSurfacePosition();
try (Transaction t = mTransactionSupplier.get()) {
if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
if (requestedVisible) {
- t.show(mSourceControl.getLeash());
+ t.show(leash);
} else {
- t.hide(mSourceControl.getLeash());
+ t.hide(leash);
}
// Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
+ t.setAlpha(leash, requestedVisible ? 1 : 0);
+ t.setPosition(leash, surfacePosition.x, surfacePosition.y);
t.apply();
}
onPerceptible(requestedVisible);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f22e8f5..0f54940b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -781,7 +781,7 @@
* <p>
* The metrics describe the size of the area the window would occupy with
* {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
- * such a window would have.
+ * such a window would have. The {@link WindowInsets} are not deducted from the bounds.
* <p>
* The value of this is based on the <b>current</b> windowing state of the system.
*
@@ -811,7 +811,7 @@
* <p>
* The metrics describe the size of the largest potential area the window might occupy with
* {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
- * such a window would have.
+ * such a window would have. The {@link WindowInsets} are not deducted from the bounds.
* <p>
* Note that this might still be smaller than the size of the physical display if certain areas
* of the display are not available to windows created in this {@link Context}.
@@ -4264,11 +4264,9 @@
* no letterbox is applied."/>
*
* <p>
- * A cutout in the corner is considered to be on the short edge: <br/>
- * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/fullscreen_corner_no_letterbox.png"
- * height="720"
- * alt="Screenshot of a fullscreen activity on a display with a cutout in the corner in
- * portrait, no letterbox is applied."/>
+ * A cutout in the corner can be considered to be on different edge in different device
+ * rotations. This behavior may vary from device to device. Use this flag is possible to
+ * letterbox your app if the display cutout is at corner.
*
* <p>
* On the other hand, should the cutout be on the long edge of the display, a letterbox will
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 26298bc..8bcc9de 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -101,9 +101,13 @@
* Returns the bounds of the area associated with this window or {@code UiContext}.
* <p>
* <b>Note that the size of the reported bounds can have different size than
- * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
- * bar areas, while {@link Display#getSize(Point)} reports the area excluding navigation bars
- * and display cutout areas. The value reported by {@link Display#getSize(Point)} can be
+ * {@link Display#getSize(Point)} based on your target API level and calling context.</b>
+ * This method reports the window size including all system
+ * bar areas, while {@link Display#getSize(Point)} can report the area excluding navigation bars
+ * and display cutout areas depending on the calling context and target SDK level. Please refer
+ * to {@link Display#getSize(Point)} for details.
+ * <p>
+ * The value reported by {@link Display#getSize(Point)} excluding system decoration areas can be
* obtained by using:
* <pre class="prettyprint">
* final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index da2bf9d..4de3a7b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -124,6 +124,7 @@
namespace: "accessibility"
name: "add_type_window_control"
is_exported: true
+ is_fixed_read_only: true
description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
bug: "320445550"
}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 163e43a..6fe0784 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -114,7 +114,6 @@
* dispatches as the progress animation updates.
*/
public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
- reset();
mLastBackEvent = event;
mCallback = callback;
mBackAnimationInProgress = true;
diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java
index 4cc7ec5..15b3c44 100644
--- a/core/java/android/window/RemoteTransition.java
+++ b/core/java/android/window/RemoteTransition.java
@@ -22,15 +22,12 @@
import android.os.IBinder;
import android.os.Parcelable;
-import com.android.internal.util.DataClass;
-
/**
* Represents a remote transition animation and information required to run it (eg. the app thread
* that needs to be boosted).
* @hide
*/
-@DataClass(genToString = true, genSetters = true, genAidl = true)
-public class RemoteTransition implements Parcelable {
+public final class RemoteTransition implements Parcelable {
/** The actual remote-transition interface used to run the transition animation. */
private @NonNull IRemoteTransition mRemoteTransition;
@@ -41,12 +38,18 @@
/** A name for this that can be used for debugging. */
private @Nullable String mDebugName;
- /** Constructs with no app thread (animation runs in shell). */
+ /**
+ * Constructs with no app thread (animation runs in shell).
+ * @hide
+ */
public RemoteTransition(@NonNull IRemoteTransition remoteTransition) {
this(remoteTransition, null /* appThread */, null /* debugName */);
}
- /** Constructs with no app thread (animation runs in shell). */
+ /**
+ * Constructs with no app thread (animation runs in shell).
+ * @hide
+ */
public RemoteTransition(@NonNull IRemoteTransition remoteTransition,
@Nullable String debugName) {
this(remoteTransition, null /* appThread */, debugName);
@@ -57,21 +60,6 @@
return mRemoteTransition.asBinder();
}
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/RemoteTransition.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
/**
* Creates a new RemoteTransition.
*
@@ -81,8 +69,8 @@
* The application thread that will be running the remote transition.
* @param debugName
* A name for this that can be used for debugging.
+ * @hide
*/
- @DataClass.Generated.Member
public RemoteTransition(
@NonNull IRemoteTransition remoteTransition,
@Nullable IApplicationThread appThread,
@@ -98,16 +86,16 @@
/**
* The actual remote-transition interface used to run the transition animation.
+ * @hide
*/
- @DataClass.Generated.Member
public @NonNull IRemoteTransition getRemoteTransition() {
return mRemoteTransition;
}
/**
* The application thread that will be running the remote transition.
+ * @hide
*/
- @DataClass.Generated.Member
public @Nullable IApplicationThread getAppThread() {
return mAppThread;
}
@@ -115,15 +103,14 @@
/**
* A name for this that can be used for debugging.
*/
- @DataClass.Generated.Member
public @Nullable String getDebugName() {
return mDebugName;
}
/**
* The actual remote-transition interface used to run the transition animation.
+ * @hide
*/
- @DataClass.Generated.Member
public @NonNull RemoteTransition setRemoteTransition(@NonNull IRemoteTransition value) {
mRemoteTransition = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -133,8 +120,8 @@
/**
* The application thread that will be running the remote transition.
+ * @hide
*/
- @DataClass.Generated.Member
public @NonNull RemoteTransition setAppThread(@NonNull IApplicationThread value) {
mAppThread = value;
return this;
@@ -143,14 +130,12 @@
/**
* A name for this that can be used for debugging.
*/
- @DataClass.Generated.Member
public @NonNull RemoteTransition setDebugName(@NonNull String value) {
mDebugName = value;
return this;
}
@Override
- @DataClass.Generated.Member
public String toString() {
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
@@ -163,7 +148,6 @@
}
@Override
- @DataClass.Generated.Member
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -178,12 +162,10 @@
}
@Override
- @DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
protected RemoteTransition(@NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -198,11 +180,8 @@
NonNull.class, null, mRemoteTransition);
this.mAppThread = appThread;
this.mDebugName = debugName;
-
- // onConstructed(); // You can define this method to get a callback
}
- @DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<RemoteTransition> CREATOR
= new Parcelable.Creator<RemoteTransition>() {
@Override
@@ -215,17 +194,4 @@
return new RemoteTransition(in);
}
};
-
- @DataClass.Generated(
- time = 1678926409863L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java",
- inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\nprivate @android.annotation.Nullable java.lang.String mDebugName\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index a2e3d40..f0144cb 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -33,6 +33,8 @@
import android.view.Surface;
import android.view.WindowInsetsController;
+import com.android.window.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -334,7 +336,8 @@
*/
public synchronized void removeReference(@ReferenceFlags int usage) {
mInternalReferences &= ~usage;
- if (mInternalReferences == 0 && mSnapshot != null && !mSnapshot.isClosed()) {
+ if (Flags.releaseSnapshotAggressively() && mInternalReferences == 0 && mSnapshot != null
+ && !mSnapshot.isClosed()) {
mSnapshot.close();
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index bcae571..4ffd880 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -54,6 +54,8 @@
import android.view.SurfaceControl;
import android.view.WindowManager;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -69,6 +71,7 @@
* Modes are only a sub-set of all the transit-types since they are per-container
* @hide
*/
+ @Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "TRANSIT_" }, value = {
TRANSIT_NONE,
TRANSIT_OPEN,
@@ -102,11 +105,11 @@
/** The container is the display. */
public static final int FLAG_IS_DISPLAY = 1 << 5;
+ // TODO(b/194540864): Once we can include all windows in transition, then replace this with
+ // something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
/**
* Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
* used to prevent seamless rotation.
- * TODO(b/194540864): Once we can include all windows in transition, then replace this with
- * something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
*/
public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
@@ -173,6 +176,7 @@
public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;
/** @hide */
+ @Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
FLAG_SHOW_WALLPAPER,
@@ -267,11 +271,11 @@
}
/** @see #getRoot */
- public void addRoot(Root other) {
+ public void addRoot(@NonNull Root other) {
mRoots.add(other);
}
- public void setAnimationOptions(AnimationOptions options) {
+ public void setAnimationOptions(@Nullable AnimationOptions options) {
mOptions = options;
}
@@ -336,6 +340,7 @@
return mRoots.get(0).mLeash;
}
+ @Nullable
public AnimationOptions getAnimationOptions() {
return mOptions;
}
@@ -601,7 +606,7 @@
* Updates the callsites of all the surfaces in this transition, which aids in the debugging of
* lingering surfaces.
*/
- public void setUnreleasedWarningCallSiteForAllSurfaces(String callsite) {
+ public void setUnreleasedWarningCallSiteForAllSurfaces(@Nullable String callsite) {
for (int i = mChanges.size() - 1; i >= 0; --i) {
mChanges.get(i).getLeash().setUnreleasedWarningCallSite(callsite);
}
@@ -613,6 +618,7 @@
* the caller's references. Use this only if you need to "send" this to a local function which
* assumes it is being called from a remote caller.
*/
+ @NonNull
public TransitionInfo localRemoteCopy() {
final TransitionInfo out = new TransitionInfo(mType, mFlags);
out.mTrack = mTrack;
@@ -891,7 +897,7 @@
return mTaskInfo;
}
- public boolean getAllowEnterPip() {
+ public boolean isAllowEnterPip() {
return mAllowEnterPip;
}
@@ -1042,6 +1048,7 @@
}
/** Represents animation options during a transition */
+ @SuppressWarnings("UserHandleName")
public static final class AnimationOptions implements Parcelable {
private int mType;
@@ -1061,7 +1068,7 @@
mType = type;
}
- public AnimationOptions(Parcel in) {
+ private AnimationOptions(Parcel in) {
mType = in.readInt();
mEnterResId = in.readInt();
mExitResId = in.readInt();
@@ -1076,14 +1083,17 @@
}
/** Make basic customized animation for a package */
- public static AnimationOptions makeCommonAnimOptions(String packageName) {
+ @NonNull
+ public static AnimationOptions makeCommonAnimOptions(@NonNull String packageName) {
AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
options.mPackageName = packageName;
return options;
}
+ /** Make custom animation from the content of LayoutParams */
+ @NonNull
public static AnimationOptions makeAnimOptionsFromLayoutParameters(
- WindowManager.LayoutParams lp) {
+ @NonNull WindowManager.LayoutParams lp) {
AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
options.mPackageName = lp.packageName;
options.mAnimations = lp.windowAnimations;
@@ -1091,7 +1101,7 @@
}
/** Add customized window animations */
- public void addOptionsFromLayoutParameters(WindowManager.LayoutParams lp) {
+ public void addOptionsFromLayoutParameters(@NonNull WindowManager.LayoutParams lp) {
mAnimations = lp.windowAnimations;
}
@@ -1111,8 +1121,11 @@
customTransition.addCustomActivityTransition(enterResId, exitResId, backgroundColor);
}
- public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
- int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
+ /** Make options for a custom animation based on anim resources */
+ @NonNull
+ public static AnimationOptions makeCustomAnimOptions(@NonNull String packageName,
+ int enterResId, int exitResId, @ColorInt int backgroundColor,
+ boolean overrideTaskTransition) {
AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
options.mPackageName = packageName;
options.mEnterResId = enterResId;
@@ -1122,6 +1135,8 @@
return options;
}
+ /** Make options for a clip-reveal animation. */
+ @NonNull
public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width,
int height) {
AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
@@ -1129,6 +1144,8 @@
return options;
}
+ /** Make options for a scale-up animation. */
+ @NonNull
public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
int height) {
AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
@@ -1136,7 +1153,9 @@
return options;
}
- public static AnimationOptions makeThumbnailAnimOptions(HardwareBuffer srcThumb,
+ /** Make options for a thumbnail-scaling animation. */
+ @NonNull
+ public static AnimationOptions makeThumbnailAnimOptions(@NonNull HardwareBuffer srcThumb,
int startX, int startY, boolean scaleUp) {
AnimationOptions options = new AnimationOptions(
scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
@@ -1145,11 +1164,15 @@
return options;
}
+ /** Make options for an animation that spans activities of different profiles. */
+ @NonNull
public static AnimationOptions makeCrossProfileAnimOptions() {
AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
return options;
}
+ /** Make options designating this as a scene-transition animation. */
+ @NonNull
public static AnimationOptions makeSceneTransitionAnimOptions() {
AnimationOptions options = new AnimationOptions(ANIM_SCENE_TRANSITION);
return options;
@@ -1175,14 +1198,17 @@
return mOverrideTaskTransition;
}
+ @Nullable
public String getPackageName() {
return mPackageName;
}
+ @NonNull
public Rect getTransitionBounds() {
return mTransitionBounds;
}
+ @Nullable
public HardwareBuffer getThumbnail() {
return mThumbnail;
}
@@ -1192,12 +1218,13 @@
}
/** Return customized activity transition if existed. */
+ @Nullable
public CustomActivityTransition getCustomActivityTransition(boolean open) {
return open ? mCustomActivityOpenTransition : mCustomActivityCloseTransition;
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mEnterResId);
dest.writeInt(mExitResId);
@@ -1247,6 +1274,7 @@
}
@Override
+ @NonNull
public String toString() {
final StringBuilder sb = new StringBuilder(32);
sb.append("{t=").append(typeToString(mType));
@@ -1261,7 +1289,7 @@
}
/** Customized activity transition. */
- public static class CustomActivityTransition implements Parcelable {
+ public static final class CustomActivityTransition implements Parcelable {
private int mCustomEnterResId;
private int mCustomExitResId;
private int mCustomBackgroundColor;
@@ -1302,7 +1330,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mCustomEnterResId);
dest.writeInt(mCustomExitResId);
dest.writeInt(mCustomBackgroundColor);
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index bd54e14..cc22576 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -113,7 +113,7 @@
/** Requested change to a display. */
@DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
- public static class DisplayChange implements Parcelable {
+ public static final class DisplayChange implements Parcelable {
private final int mDisplayId;
@Nullable private Rect mStartAbsBounds = null;
@Nullable private Rect mEndAbsBounds = null;
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index f94766e..0590c40 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -85,3 +85,10 @@
description: "Matches the App Header density to that of the app window, instead of SysUI's"
bug: "332414819"
}
+
+flag {
+ name: "enable_themed_app_headers"
+ namespace: "lse_desktop_experience"
+ description: "Makes the App Header style adapt to the system's and app's light/dark theme"
+ bug: "328668781"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ee3e34f..f08f5b8 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -163,4 +163,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "release_snapshot_aggressively"
+ namespace: "windowing_frontend"
+ description: "Actively release task snapshot memory"
+ bug: "238206323"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 84715aa..17adee4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -917,7 +917,7 @@
mSystemWindowInsets = insets.getSystemWindowInsets();
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
+ mSystemWindowInsets.right, mSystemWindowInsets.bottom);
resetButtonBar();
@@ -946,7 +946,7 @@
if (mSystemWindowInsets != null) {
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
+ mSystemWindowInsets.right, mSystemWindowInsets.bottom);
}
}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
new file mode 100644
index 0000000..f306b0b
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -0,0 +1,244 @@
+/*
+ * 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.pm.pkg.component;
+
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+
+import android.aconfig.nano.Aconfig;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.aconfig.nano.Aconfig.parsed_flags;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Flags;
+import android.content.res.XmlResourceParser;
+import android.os.Environment;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class that manages a cache of all device feature flags and their default + override values.
+ * This class performs a very similar job to the one in {@code SettingsProvider}, with an important
+ * difference: this is a part of system server and is available for the server startup. Package
+ * parsing happens at the startup when {@code SettingsProvider} isn't available yet, so we need an
+ * own copy of the code here.
+ * @hide
+ */
+public class AconfigFlags {
+ private static final String LOG_TAG = "AconfigFlags";
+
+ private static final List<String> sTextProtoFilesOnDevice = List.of(
+ "/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
+ "/product/etc/aconfig_flags.pb",
+ "/vendor/etc/aconfig_flags.pb");
+
+ private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+
+ public AconfigFlags() {
+ if (!Flags.manifestFlagging()) {
+ Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
+ return;
+ }
+ for (String fileName : sTextProtoFilesOnDevice) {
+ try (var inputStream = new FileInputStream(fileName)) {
+ loadAconfigDefaultValues(inputStream.readAllBytes());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+ }
+ }
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // Server overrides are only accessible to the system, no need to even try loading them
+ // in user processes.
+ loadServerOverrides();
+ }
+ }
+
+ private void loadServerOverrides() {
+ // Reading the proto files is enough for READ_ONLY flags but if it's a READ_WRITE flag
+ // (which you can check with `flag.getPermission() == flag_permission.READ_WRITE`) then we
+ // also need to check if there is a value pushed from the server in the file
+ // `/data/system/users/0/settings_config.xml`. It will be in a <setting> node under the
+ // root <settings> node with "name" attribute == "flag_namespace/flag_package.flag_name".
+ // The "value" attribute will be true or false.
+ //
+ // The "name" attribute could also be "<namespace>/flag_namespace?flag_package.flag_name"
+ // (prefixed with "staged/" or "device_config_overrides/" and a different separator between
+ // namespace and name). This happens when a flag value is overridden either with a pushed
+ // one from the server, or from the local command.
+ // When the device reboots during package parsing, the staged value will still be there and
+ // only later it will become a regular/non-staged value after SettingsProvider is
+ // initialized.
+ //
+ // In all cases, when there is more than one value, the priority is:
+ // device_config_overrides > staged > default
+ //
+
+ final var settingsFile = new File(Environment.getUserSystemDirectory(0),
+ "settings_config.xml");
+ try (var inputStream = new FileInputStream(settingsFile)) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+ if (parser.next() != XmlPullParser.END_TAG && "settings".equals(parser.getName())) {
+ final var flagPriority = new ArrayMap<String, Integer>();
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (!"setting".equals(parser.getName())) {
+ continue;
+ }
+ String name = parser.getAttributeValue(null, "name");
+ final String value = parser.getAttributeValue(null, "value");
+ if (name == null || value == null) {
+ continue;
+ }
+ // A non-boolean setting is definitely not an Aconfig flag value.
+ if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) {
+ continue;
+ }
+ final var overridePrefix = "device_config_overrides/";
+ final var stagedPrefix = "staged/";
+ String separator = "/";
+ String prefix = "default";
+ int priority = 0;
+ if (name.startsWith(overridePrefix)) {
+ prefix = overridePrefix;
+ name = name.substring(overridePrefix.length());
+ separator = ":";
+ priority = 20;
+ } else if (name.startsWith(stagedPrefix)) {
+ prefix = stagedPrefix;
+ name = name.substring(stagedPrefix.length());
+ separator = "*";
+ priority = 10;
+ }
+ final String flagPackageAndName = parseFlagPackageAndName(name, separator);
+ if (flagPackageAndName == null) {
+ continue;
+ }
+ // We ignore all settings that aren't for flags. We'll know they are for flags
+ // if they correspond to flags read from the proto files.
+ if (!mFlagValues.containsKey(flagPackageAndName)) {
+ continue;
+ }
+ Slog.d(LOG_TAG, "Found " + prefix
+ + " Aconfig flag value for " + flagPackageAndName + " = " + value);
+ final Integer currentPriority = flagPriority.get(flagPackageAndName);
+ if (currentPriority != null && currentPriority >= priority) {
+ Slog.i(LOG_TAG, "Skipping " + prefix + " flag " + flagPackageAndName
+ + " because of the existing one with priority " + currentPriority);
+ continue;
+ }
+ flagPriority.put(flagPackageAndName, priority);
+ mFlagValues.put(flagPackageAndName, Boolean.parseBoolean(value));
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(LOG_TAG, "Failed to read Aconfig values from settings_config.xml", e);
+ }
+ }
+
+ private static String parseFlagPackageAndName(String fullName, String separator) {
+ int index = fullName.indexOf(separator);
+ if (index < 0) {
+ return null;
+ }
+ return fullName.substring(index + 1);
+ }
+
+ private void loadAconfigDefaultValues(byte[] fileContents) throws IOException {
+ parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+ for (parsed_flag flag : parsedFlags.parsedFlag) {
+ String flagPackageAndName = flag.package_ + "." + flag.name;
+ boolean flagValue = (flag.state == Aconfig.ENABLED);
+ Slog.v(LOG_TAG, "Read Aconfig default flag value "
+ + flagPackageAndName + " = " + flagValue);
+ mFlagValues.put(flagPackageAndName, flagValue);
+ }
+ }
+
+ /**
+ * Get the flag value, or null if the flag doesn't exist.
+ * @param flagPackageAndName Full flag name formatted as 'package.flag'
+ * @return the current value of the given Aconfig flag, or null if there is no such flag
+ */
+ @Nullable
+ public Boolean getFlagValue(@NonNull String flagPackageAndName) {
+ Boolean value = mFlagValues.get(flagPackageAndName);
+ Slog.d(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+ return value;
+ }
+
+ /**
+ * Check if the element in {@code parser} should be skipped because of the feature flag.
+ * @param parser XML parser object currently parsing an element
+ * @return true if the element is disabled because of its feature flag
+ */
+ public boolean skipCurrentElement(@NonNull XmlResourceParser parser) {
+ if (!Flags.manifestFlagging()) {
+ return false;
+ }
+ String featureFlag = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "featureFlag");
+ if (featureFlag == null) {
+ return false;
+ }
+ featureFlag = featureFlag.strip();
+ boolean negated = false;
+ if (featureFlag.startsWith("!")) {
+ negated = true;
+ featureFlag = featureFlag.substring(1).strip();
+ }
+ final Boolean flagValue = getFlagValue(featureFlag);
+ if (flagValue == null) {
+ Slog.w(LOG_TAG, "Skipping element " + parser.getName()
+ + " due to unknown feature flag " + featureFlag);
+ return true;
+ }
+ // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
+ if (flagValue == negated) {
+ Slog.v(LOG_TAG, "Skipping element " + parser.getName()
+ + " behind feature flag " + featureFlag + " = " + flagValue);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add Aconfig flag values for testing flagging of manifest entries.
+ * @param flagValues A map of flag name -> value.
+ */
+ @VisibleForTesting
+ public void addFlagValuesForTesting(@NonNull Map<String, Boolean> flagValues) {
+ mFlagValues.putAll(flagValues);
+ }
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
index db08005..8858f94 100644
--- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
@@ -61,6 +61,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
if ("meta-data".equals(parser.getName())) {
diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
index 0b04591..bb01581 100644
--- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
@@ -27,6 +27,7 @@
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -80,6 +81,9 @@
}
return input.success(prefixes);
} else if (type == XmlPullParser.START_TAG) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) {
ParseResult<String> parsedPrefix =
readFingerprintPrefixValue(input, res, parser);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index 9f71d88..55baa53 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -393,6 +393,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
if (parser.getName().equals("intent-filter")) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index 05728ee..da48b23 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -99,6 +99,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String nodeName = parser.getName();
@@ -197,6 +200,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String nodeName = parser.getName();
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 12aff1c..6af2a29 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -36,6 +36,7 @@
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -173,6 +174,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
String name = parser.getName();
final ParseResult result;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 4ac542f8..c68ea2d 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -34,6 +34,7 @@
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -137,6 +138,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult parseResult;
switch (parser.getName()) {
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 1dcd893..44fedb1 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -90,6 +90,7 @@
import com.android.internal.os.ClassLoaderFactory;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.permission.CompatibilityPermissionInfo;
+import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.pm.pkg.component.ComponentMutateUtils;
import com.android.internal.pm.pkg.component.ComponentParseUtils;
import com.android.internal.pm.pkg.component.InstallConstraintsTagParser;
@@ -292,6 +293,7 @@
@NonNull
private final List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
private final Callback mCallback;
+ private static final AconfigFlags sAconfigFlags = new AconfigFlags();
public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMetrics,
@NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
@@ -761,6 +763,9 @@
if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String tagName = parser.getName();
@@ -837,6 +842,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
ParsedMainComponent mainComponent = null;
@@ -980,6 +988,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
String tagName = parser.getName();
final ParseResult result;
@@ -1599,6 +1610,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final String innerTagName = parser.getName();
if (innerTagName.equals("uses-feature")) {
@@ -1839,6 +1853,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
if (parser.getName().equals("intent")) {
ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
null /*className*/, pkg, res, parser, true /*allowGlobs*/,
@@ -2185,6 +2202,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final ParseResult result;
String tagName = parser.getName();
@@ -2773,6 +2793,9 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
+ if (sAconfigFlags.skipCurrentElement(parser)) {
+ continue;
+ }
final String nodeName = parser.getName();
if (nodeName.equals("additional-certificate")) {
@@ -3458,4 +3481,11 @@
@NonNull Set<String> getInstallConstraintsAllowlist();
}
+
+ /**
+ * Getter for the flags object
+ */
+ public static AconfigFlags getAconfigFlags() {
+ return sAconfigFlags;
+ }
}
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 1340156..4a3dfbe 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -49,7 +49,7 @@
return false;
}
- public boolean isRunningOnRavenwood$ravenwood() {
+ private boolean isRunningOnRavenwood$ravenwood() {
return true;
}
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d9c40c3..61eaa52 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -439,6 +439,7 @@
"android_database_SQLiteConnection.cpp",
"android_database_SQLiteGlobal.cpp",
"android_database_SQLiteDebug.cpp",
+ "android_database_SQLiteRawStatement.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index acef609..59d18b8 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -89,6 +89,7 @@
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_database_SQLiteRawStatement(JNIEnv* env);
extern int register_android_os_FileObserver(JNIEnv* env);
extern int register_android_os_MessageQueue(JNIEnv* env);
extern int register_android_os_Parcel(JNIEnv* env);
@@ -128,6 +129,8 @@
REG_JNI(register_android_database_SQLiteConnection)},
{"android.database.sqlite.SQLiteGlobal", REG_JNI(register_android_database_SQLiteGlobal)},
{"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
+ {"android.database.sqlite.SQLiteRawStatement",
+ REG_JNI(register_android_database_SQLiteRawStatement)},
#endif
{"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
{"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5fa13ba..405324b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2589,6 +2589,8 @@
<li>The framework will set {@link android.R.attr#statusBarColor},
{@link android.R.attr#navigationBarColor}, and
{@link android.R.attr#navigationBarDividerColor} to transparent.
+ <li>The frameworks will send Configuration no longer considering system insets.
+ The Configuration will be stable regardless of the system insets change.
</ul>
<p>If this is true, the edge-to-edge enforcement won't be applied. However, this
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 4d7c009..67cceb5 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -60,6 +60,9 @@
<!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
<shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
+ <!-- Burkina Faso: 1-4 digits (standard system default, not country specific) -->
+ <shortcode country="bf" pattern="\\d{1,4}" free="3558" />
+
<!-- Bulgaria: 4-5 digits, plus EU -->
<shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" />
@@ -175,8 +178,8 @@
<!-- Israel: 1-5 digits, known premium codes listed -->
<shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
- <!-- Iran: 4-6 digits, known premium codes listed -->
- <shortcode country="ir" pattern="\\d{4,6}" free="700791|700792" />
+ <!-- Iran: 4-8 digits, known premium codes listed -->
+ <shortcode country="ir" pattern="\\d{4,8}" free="700791|700792|100016|30008360" />
<!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -352,7 +355,7 @@
<shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" />
<!-- Yemen -->
- <shortcode country="ye" pattern="\\d{1,4}" free="5081" />
+ <shortcode country="ye" pattern="\\d{1,4}" free="5079" />
<!-- Zimbabwe -->
<shortcode country="zw" pattern="\\d{1,5}" free="33679" />
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index bd13276..5a7b0bb 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -25,6 +25,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.text.FontConfig;
import android.util.SparseIntArray;
@@ -151,6 +152,7 @@
* @return A variable font family. null if a variable font cannot be built from the given
* fonts.
*/
+ @SuppressLint("BuilderSetStyle")
@FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public @Nullable FontFamily buildVariableFamily() {
int variableFamilyType = analyzeAndResolveVariableType(mFonts);
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7d55928..5a1086c 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -23,6 +23,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.os.Build;
import android.os.LocaleList;
@@ -314,6 +315,7 @@
* @param config an override line break config
* @return This {@code Builder}.
*/
+ @SuppressLint("BuilderSetStyle")
@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
public @NonNull Builder merge(@NonNull LineBreakConfig config) {
if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) {
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 785e30d..6ca6517 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
@@ -322,7 +322,7 @@
null,
new Rect(change.getStartAbsBounds()),
taskInfo,
- change.getAllowEnterPip(),
+ change.isAllowEnterPip(),
INVALID_WINDOW_TYPE
);
target.setWillShowImeOnTarget(
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 01364d1..6968317 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
@@ -46,6 +46,7 @@
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -82,6 +83,7 @@
@Provides
static Optional<PipController> providePipController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -97,9 +99,10 @@
return Optional.empty();
} else {
return Optional.ofNullable(PipController.create(
- context, shellInit, shellController, displayController, displayInsetsController,
- pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
- taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor));
+ context, shellInit, shellCommandHandler, shellController, displayController,
+ displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
+ pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
+ pipTransitionState, mainExecutor));
}
}
@@ -129,6 +132,7 @@
@Provides
static PipTouchHandler providePipTouchHandler(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
PhonePipMenuController menuPhoneController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
@@ -140,10 +144,10 @@
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
- floatingContentCoordinator, pipUiEventLogger, mainExecutor,
- pipPerfHintControllerOptional);
+ return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController,
+ pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler,
+ sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger,
+ mainExecutor, pipPerfHintControllerOptional);
}
@WMSingleton
@@ -163,7 +167,7 @@
@WMSingleton
@Provides
- static PipTransitionState providePipStackListenerController() {
- return new PipTransitionState();
+ static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) {
+ return new PipTransitionState(handler);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 6a7d297..a42ca19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -16,10 +16,9 @@
package com.android.wm.shell.draganddrop;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
@@ -47,7 +46,6 @@
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
-import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
@@ -265,13 +263,14 @@
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+ // Put BAL flags to avoid activity start aborted.
+ baseActivityOpts.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ baseActivityOpts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
final Bundle opts = baseActivityOpts.toBundle();
if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
}
- // Put BAL flags to avoid activity start aborted.
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
if (isTask) {
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 a12882f..f5afeea 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
@@ -58,9 +58,12 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import java.io.PrintWriter;
+
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
@@ -72,6 +75,7 @@
private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -111,6 +115,7 @@
private PipController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -123,6 +128,7 @@
PipTransitionState pipTransitionState,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
@@ -146,6 +152,7 @@
*/
public static PipController create(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -162,13 +169,14 @@
"%s: Device doesn't support Pip feature", TAG);
return null;
}
- return new PipController(context, shellInit, shellController, displayController,
- displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
- pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState,
- mainExecutor);
+ return new PipController(context, shellInit, shellCommandHandler, shellController,
+ displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
+ pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
+ pipTransitionState, mainExecutor);
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
@@ -338,6 +346,14 @@
}
}
+ private void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = " ";
+ pw.println(TAG);
+ mPipBoundsAlgorithm.dump(pw, innerPrefix);
+ mPipBoundsState.dump(pw, innerPrefix);
+ mPipDisplayLayoutState.dump(pw, innerPrefix);
+ }
+
/**
* The interface for calls from outside the host process.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index be10151..aed493f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -696,6 +696,19 @@
case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
+ if (mPipBoundsState.getBounds().equals(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) {
+ // Avoid scheduling transitions for bounds that don't change, such transition is
+ // a no-op and would be aborted.
+ settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ cleanUpHighPerfSessionMaybe();
+ // SCHEDULED_BOUNDS_CHANGE can have multiple active listeners making
+ // actual changes (e.g. PipTouchHandler). So post state update onto handler,
+ // to run after synchronous dispatch is complete.
+ mPipTransitionState.postState(PipTransitionState.CHANGED_PIP_BOUNDS);
+ break;
+ }
+
// If touch is turned off and we are in a fling animation, schedule a transition.
mWaitingForBoundsChangeTransition = true;
mPipScheduler.scheduleAnimateResizePip(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index b55a41d..7dffe54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -514,6 +514,20 @@
switch (newState) {
case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
+
+ if (mPipBoundsState.getBounds().equals(mLastResizeBounds)) {
+ // If the bounds are invariant move the destination bounds by a single pixel
+ // to top/bottom to avoid a no-op transition. This trick helps keep the
+ // animation a part of the transition.
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mPipBoundsState.getBounds());
+
+ // Move to the top if closer to the bottom edge and vice versa.
+ boolean inTopHalf = snapFraction < 1.5 || snapFraction > 3.5;
+ int offsetY = inTopHalf ? 1 : -1;
+ mLastResizeBounds.offset(0 /* dx */, offsetY);
+ }
+
mWaitingForBoundsChangeTransition = true;
mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
break;
@@ -527,17 +541,14 @@
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
Rect destinationBounds = extra.getParcelable(
PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
- startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
- destinationBounds.left, destinationBounds.top);
startTx.apply();
// All motion operations have actually finished, so make bounds cache updates.
+ mUpdateResizeBoundsCallback.accept(destinationBounds);
cleanUpHighPerfSessionMaybe();
// Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
-
- mUpdateResizeBoundsCallback.accept(destinationBounds);
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 319d199..56a465a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -64,6 +64,7 @@
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -81,6 +82,7 @@
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@NonNull private final PipBoundsState mPipBoundsState;
@NonNull private final PipTransitionState mPipTransitionState;
@@ -170,6 +172,7 @@
@SuppressLint("InflateParams")
public PipTouchHandler(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
@@ -182,6 +185,7 @@
ShellExecutor mainExecutor,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -235,6 +239,7 @@
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
reloadResources();
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mMotionHelper.init();
mPipResizeGestureHandler.init();
mPipDismissTargetHandler.init();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 8204d41..9d599ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
@@ -26,6 +27,7 @@
import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -109,6 +111,13 @@
private int mState;
//
+ // Dependencies
+ //
+
+ @ShellMainThread
+ private final Handler mMainHandler;
+
+ //
// Swipe up to enter PiP related state
//
@@ -149,6 +158,10 @@
private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
+ public PipTransitionState(@ShellMainThread Handler handler) {
+ mMainHandler = handler;
+ }
+
/**
* @return the state of PiP in the context of transitions.
*/
@@ -182,6 +195,32 @@
}
}
+ /**
+ * Posts the state update for PiP in the context of transitions onto the main handler.
+ *
+ * <p>This is done to guarantee that any callback dispatches for the present state are
+ * complete. This is relevant for states that have multiple listeners, such as
+ * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with
+ * the actual transition scheduling.</p>
+ */
+ public void postState(@TransitionState int state) {
+ postState(state, null /* extra */);
+ }
+
+ /**
+ * Posts the state update for PiP in the context of transitions onto the main handler.
+ *
+ * <p>This is done to guarantee that any callback dispatches for the present state are
+ * complete. This is relevant for states that have multiple listeners, such as
+ * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with
+ * the actual transition scheduling.</p>
+ *
+ * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
+ */
+ public void postState(@TransitionState int state, @Nullable Bundle extra) {
+ mMainHandler.post(() -> setState(state, extra));
+ }
+
private void dispatchPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState, @Nullable Bundle extra) {
mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra));
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 4299088..9f8cb62 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
@@ -16,10 +16,8 @@
package com.android.wm.shell.splitscreen;
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -48,7 +46,6 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
-import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -1888,13 +1885,15 @@
}
private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
+ ActivityOptions options = ActivityOptions.fromBundle(opts);
if (launchTarget != null) {
- opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
+ options.setLaunchRootTask(launchTarget.mRootTaskInfo.token);
}
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
// will be canceled.
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
+ options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ opts.putAll(options.toBundle());
}
void updateActivityOptions(Bundle opts, @SplitPosition int position) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 1be3b02..8a49a73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,8 +93,7 @@
Choreographer choreographer,
SyncTransactionQueue syncQueue,
ResizeHandleSizeRepository resizeHandleSizeRepository) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface,
- taskInfo.getConfiguration());
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface);
mHandler = handler;
mChoreographer = choreographer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bb89adf..9c92791 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -140,13 +140,12 @@
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
- Configuration windowDecorConfig,
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
ResizeHandleSizeRepository resizeHandleSizeRepository) {
- this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ this (context, displayController, taskOrganizer, taskInfo, taskSurface,
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
resizeHandleSizeRepository, SurfaceControl.Builder::new,
SurfaceControl.Transaction::new, WindowContainerTransaction::new,
@@ -159,7 +158,6 @@
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
- Configuration windowDecorConfig,
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
@@ -170,7 +168,7 @@
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
surfaceControlViewHostFactory);
@@ -964,17 +962,12 @@
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
ResizeHandleSizeRepository resizeHandleSizeRepository) {
- final Configuration windowDecorConfig =
- DesktopModeStatus.isDesktopDensityOverrideSet()
- ? context.getResources().getConfiguration() // Use system context
- : taskInfo.configuration; // Use task configuration
return new DesktopModeWindowDecoration(
context,
displayController,
taskOrganizer,
taskInfo,
taskSurface,
- windowDecorConfig,
handler,
choreographer,
syncQueue,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 5418254..2cbe472 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -145,9 +146,8 @@
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
- Configuration windowDecorConfig) {
- this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ SurfaceControl taskSurface) {
+ this(context, displayController, taskOrganizer, taskInfo, taskSurface,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
new SurfaceControlViewHostFactory() {});
@@ -159,7 +159,6 @@
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
@NonNull SurfaceControl taskSurface,
- Configuration windowDecorConfig,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
@@ -176,8 +175,6 @@
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mWindowDecorConfig = windowDecorConfig;
- mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
}
/**
@@ -220,8 +217,11 @@
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final int oldDensityDpi = mWindowDecorConfig.densityDpi;
- final int oldNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ final int oldDensityDpi = mWindowDecorConfig != null
+ ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
+ final int oldNightMode = mWindowDecorConfig != null
+ ? (mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ : Configuration.UI_MODE_NIGHT_UNDEFINED;
mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig
: mTaskInfo.getConfiguration();
final int newDensityDpi = mWindowDecorConfig.densityDpi;
@@ -230,7 +230,8 @@
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId
|| oldLayoutResId != mLayoutResId
- || oldNightMode != newNightMode) {
+ || oldNightMode != newNightMode
+ || mDecorWindowContext == null) {
releaseViews(wct);
if (!obtainDisplayOrRegisterListener()) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
index bd8ac37..f3f3c37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2;
import android.os.Bundle;
+import android.os.Handler;
import android.os.Parcelable;
import android.testing.AndroidTestingRunner;
@@ -29,6 +30,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
/**
* Unit test against {@link PhoneSizeSpecSource}.
@@ -42,9 +44,12 @@
private PipTransitionState.PipTransitionStateChangedListener mStateChangedListener;
private Parcelable mEmptyParcelable;
+ @Mock
+ private Handler mMainHandler;
+
@Before
public void setUp() {
- mPipTransitionState = new PipTransitionState();
+ mPipTransitionState = new PipTransitionState(mMainHandler);
mPipTransitionState.setState(PipTransitionState.UNDEFINED);
mEmptyParcelable = new Bundle();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d7c3835..d18fec2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -16,10 +16,7 @@
package com.android.wm.shell.splitscreen;
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -31,8 +28,9 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
@@ -45,6 +43,7 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -54,7 +53,6 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.RemoteTransition;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -343,14 +341,14 @@
@Test
public void testAddActivityOptions_addsBackgroundActivitiesFlags() {
- Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
+ Bundle bundle = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */);
+ ActivityOptions options = ActivityOptions.fromBundle(bundle);
- assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class),
- mMainStage.mRootTaskInfo.token);
- assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED));
- assertTrue(options.getBoolean(
- KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
+ assertThat(options.getLaunchRootTask()).isEqualTo(mMainStage.mRootTaskInfo.token);
+ assertThat(options.getPendingIntentBackgroundActivityStartMode())
+ .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission()).isTrue();
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index cff9313..e737861 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -346,7 +346,7 @@
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
- mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
+ mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
mMockResizeHandleSizeRepository, SurfaceControl.Builder::new,
mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8b8cd11..4831081 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -49,7 +49,6 @@
import android.app.ActivityManager;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
@@ -134,7 +133,6 @@
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
- private Configuration mWindowConfiguration = new Configuration();
private int mCaptionMenuWidthId;
@Before
@@ -303,7 +301,6 @@
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
@@ -314,14 +311,16 @@
verify(mMockWindowContainerTransaction, never())
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
+ final SurfaceControl.Transaction t2 = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t2);
taskInfo.isVisible = false;
windowDecor.relayout(taskInfo);
- final InOrder releaseOrder = inOrder(t, mMockSurfaceControlViewHost);
+ final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
releaseOrder.verify(mMockSurfaceControlViewHost).release();
- releaseOrder.verify(t).remove(captionContainerSurface);
- releaseOrder.verify(t).remove(decorContainerSurface);
- releaseOrder.verify(t).apply();
+ releaseOrder.verify(t2).remove(captionContainerSurface);
+ releaseOrder.verify(t2).remove(decorContainerSurface);
+ releaseOrder.verify(t2).apply();
// Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
verify(mMockWindowContainerTransaction, Mockito.times(2))
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -836,7 +835,7 @@
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, mMockTaskSurface, mWindowConfiguration,
+ taskInfo, mMockTaskSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
@@ -877,16 +876,15 @@
TestWindowDecoration(Context context, DisplayController displayController,
ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
- Configuration windowConfiguration,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
- windowConfiguration, surfaceControlBuilderSupplier,
- surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
- surfaceControlSupplier, surfaceControlViewHostFactory);
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ windowContainerTransactionSupplier, surfaceControlSupplier,
+ surfaceControlViewHostFactory);
}
@Override
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 3d0a534..785aef3 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -688,8 +688,8 @@
static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
jobject padding, jobject bitmapFactoryOptions, jlong inBitmapHandle, jlong colorSpaceHandle) {
-#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
- return nullObjectReturn("Not supported on Windows");
+#ifdef _WIN32 // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
+ return nullObjectReturn("Not supported on Windows");
#else
NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index fa08658..bdd3c73 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -16,6 +16,9 @@
package android.media;
+import static android.media.MediaFormat.KEY_AAC_DRC_ALBUM_MODE;
+import static android.media.MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR;
+import static android.media.MediaFormat.KEY_AAC_DRC_BOOST_FACTOR;
import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
@@ -142,6 +145,18 @@
filteredBundle.putInt(KEY_AAC_DRC_EFFECT_TYPE,
bundle.getInt(KEY_AAC_DRC_EFFECT_TYPE));
}
+ if (bundle.containsKey(KEY_AAC_DRC_BOOST_FACTOR)) {
+ filteredBundle.putInt(KEY_AAC_DRC_BOOST_FACTOR,
+ bundle.getInt(KEY_AAC_DRC_BOOST_FACTOR));
+ }
+ if (bundle.containsKey(KEY_AAC_DRC_ATTENUATION_FACTOR)) {
+ filteredBundle.putInt(KEY_AAC_DRC_ATTENUATION_FACTOR,
+ bundle.getInt(KEY_AAC_DRC_ATTENUATION_FACTOR));
+ }
+ if (bundle.containsKey(KEY_AAC_DRC_ALBUM_MODE)) {
+ filteredBundle.putInt(KEY_AAC_DRC_ALBUM_MODE,
+ bundle.getInt(KEY_AAC_DRC_ALBUM_MODE));
+ }
return filteredBundle;
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 223b432c..4059291 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -109,7 +109,7 @@
try {
final Callback c = Objects.requireNonNull(callback);
if (handler == null) {
- handler = new Handler();
+ handler = new Handler(mContext.getMainLooper());
}
mCallbacks.put(c, new CallbackRecord(c, handler));
} catch (NullPointerException e) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index a488756..70462ef 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -614,12 +614,11 @@
}
/**
- * Override to handle changes to the audio info.
+ * Signals a change in the session's {@link PlaybackInfo PlaybackInfo}.
*
- * @param info The current audio info for this session.
+ * @param playbackInfo The latest known state of the session's playback info.
*/
- public void onAudioInfoChanged(PlaybackInfo info) {
- }
+ public void onAudioInfoChanged(@NonNull PlaybackInfo playbackInfo) {}
}
/**
@@ -1182,7 +1181,7 @@
}
@Override
- public void onVolumeInfoChanged(PlaybackInfo info) {
+ public void onVolumeInfoChanged(@NonNull PlaybackInfo info) {
MediaController controller = mController.get();
if (controller != null) {
controller.postMessage(MSG_UPDATE_VOLUME, info, null);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 7cd7e7ab..7150b54 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -91,7 +91,7 @@
boolean enableReaderOption(boolean enable);
boolean isObserveModeSupported();
boolean isObserveModeEnabled();
- boolean setObserveMode(boolean enabled);
+ boolean setObserveMode(boolean enabled, String pkg);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
boolean setWlcEnabled(boolean enable);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 06098de..698df28 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1268,8 +1268,12 @@
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
public boolean setObserveModeEnabled(boolean enabled) {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " observe mode APIs");
+ }
try {
- return sService.setObserveMode(enabled);
+ return sService.setObserveMode(enabled, mContext.getPackageName());
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
index 03b5c19..723c187 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
@@ -348,13 +348,13 @@
label: ','
base: ','
shift: '\''
- ralt: '_'
+ ralt: '\u00af'
}
key PERIOD {
label: '.'
base: '.'
- ralt: '-'
+ ralt: '\u00ad'
}
key SLASH {
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index cdd5c25..6052be3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -20,7 +20,7 @@
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
<item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
- <item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
+ <item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
</style>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt
new file mode 100644
index 0000000..9350f98
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+interface IAppOpsPermissionController {
+ val isAllowedFlow: Flow<Boolean>
+ fun setAllowed(allowed: Boolean)
+}
+
+class AppOpsPermissionController(
+ context: Context,
+ private val app: ApplicationInfo,
+ appOps: AppOps,
+ private val permission: String,
+ private val packageManagers: IPackageManagers = PackageManagers,
+ private val appOpsController: IAppOpsController = AppOpsController(context, app, appOps),
+) : IAppOpsPermissionController {
+ override val isAllowedFlow: Flow<Boolean> = appOpsController.modeFlow.map { mode ->
+ when (mode) {
+ AppOpsManager.MODE_ALLOWED -> true
+
+ AppOpsManager.MODE_DEFAULT -> {
+ with(packageManagers) { app.hasGrantPermission(permission) }
+ }
+
+ else -> false
+ }
+ }.conflate().onEach { Log.d(TAG, "isAllowed: $it") }.flowOn(Dispatchers.Default)
+
+ override fun setAllowed(allowed: Boolean) {
+ appOpsController.setAllowed(allowed)
+ }
+
+ private companion object {
+ private const val TAG = "AppOpsPermissionControl"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 37b1d73..120b75e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -20,13 +20,14 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spaprivileged.model.app.AppOps
-import com.android.settingslib.spaprivileged.model.app.AppOpsController
+import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController
import com.android.settingslib.spaprivileged.model.app.AppRecord
-import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import kotlinx.coroutines.flow.Flow
@@ -37,7 +38,7 @@
override val app: ApplicationInfo,
val hasRequestBroaderPermission: Boolean,
val hasRequestPermission: Boolean,
- var appOpsController: IAppOpsController,
+ var appOpsPermissionController: IAppOpsPermissionController,
) : AppRecord
abstract class AppOpPermissionListModel(
@@ -70,8 +71,8 @@
private val notChangeablePackages =
setOf("android", "com.android.systemui", context.packageName)
- private fun createAppOpsController(app: ApplicationInfo) =
- AppOpsController(context, app, appOps)
+ private fun createAppOpsPermissionController(app: ApplicationInfo) =
+ AppOpsPermissionController(context, app, appOps, permission)
private fun createRecord(
app: ApplicationInfo,
@@ -84,7 +85,7 @@
app.hasRequestPermission(it)
} ?: false,
hasRequestPermission = hasRequestPermission,
- appOpsController = createAppOpsController(app),
+ appOpsPermissionController = createAppOpsPermissionController(app),
)
}
@@ -117,14 +118,20 @@
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
recordListFlow.filterItem(::isChangeable)
+ /**
+ * Defining the default behavior as permissible as long as the package requested this permission
+ * (This means pre-M gets approval during install time; M apps gets approval during runtime).
+ */
@Composable
- override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? =
- isAllowed(
- record = record,
- appOpsController = record.appOpsController,
- permission = permission,
- packageManagers = packageManagers,
- )
+ override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? {
+ if (record.hasRequestBroaderPermission) {
+ // Broader permission trumps the specific permission.
+ return { true }
+ }
+ val isAllowed by record.appOpsPermissionController.isAllowedFlow
+ .collectAsStateWithLifecycle(initialValue = null)
+ return { isAllowed }
+ }
override fun isChangeable(record: AppOpPermissionRecord) =
record.hasRequestPermission &&
@@ -132,36 +139,6 @@
record.app.packageName !in notChangeablePackages
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
- record.appOpsController.setAllowed(newAllowed)
- }
-}
-
-/**
- * Defining the default behavior as permissible as long as the package requested this permission
- * (This means pre-M gets approval during install time; M apps gets approval during runtime).
- */
-@Composable
-internal fun isAllowed(
- record: AppOpPermissionRecord,
- appOpsController: IAppOpsController,
- permission: String,
- packageManagers: IPackageManagers = PackageManagers,
-): () -> Boolean? {
- if (record.hasRequestBroaderPermission) {
- // Broader permission trumps the specific permission.
- return { true }
- }
-
- val mode = appOpsController.modeFlow.collectAsStateWithLifecycle(initialValue = null)
- return {
- when (mode.value) {
- null -> null
- AppOpsManager.MODE_ALLOWED -> true
- AppOpsManager.MODE_DEFAULT -> {
- with(packageManagers) { record.app.hasGrantPermission(permission) }
- }
-
- else -> false
- }
+ record.appOpsPermissionController.setAllowed(newAllowed)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
new file mode 100644
index 0000000..9f80b92
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsPermissionControllerTest {
+
+ private val appOpsManager = mock<AppOpsManager>()
+ private val packageManager = mock<PackageManager>()
+ private val packageManagers = mock<IPackageManagers>()
+ private val appOpsController = mock<IAppOpsController>()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { appOpsManager } doReturn appOpsManager
+ on { packageManager } doReturn packageManager
+ }
+
+ @Test
+ fun isAllowedFlow_appOpsAllowed_returnTrue() = runBlocking {
+ appOpsController.stub {
+ on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ALLOWED)
+ }
+ val controller = AppOpsPermissionController(
+ context = context,
+ app = APP,
+ appOps = AppOps(op = OP),
+ permission = PERMISSION,
+ appOpsController = appOpsController,
+ )
+
+ val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
+ fun isAllowedFlow_appOpsDefaultAndPermissionGranted_returnTrue() = runBlocking {
+ appOpsController.stub {
+ on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT)
+ }
+ packageManagers.stub {
+ on { APP.hasGrantPermission(PERMISSION) } doReturn true
+ }
+ val controller = AppOpsPermissionController(
+ context = context,
+ app = APP,
+ appOps = AppOps(op = OP),
+ permission = PERMISSION,
+ packageManagers = packageManagers,
+ appOpsController = appOpsController,
+ )
+
+ val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
+ fun isAllowedFlow_appOpsDefaultAndPermissionNotGranted_returnFalse() = runBlocking {
+ appOpsController.stub {
+ on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT)
+ }
+ packageManagers.stub {
+ on { APP.hasGrantPermission(PERMISSION) } doReturn false
+ }
+ val controller = AppOpsPermissionController(
+ context = context,
+ app = APP,
+ appOps = AppOps(op = OP),
+ permission = PERMISSION,
+ packageManagers = packageManagers,
+ appOpsController = appOpsController,
+ )
+
+ val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun isAllowedFlow_appOpsError_returnFalse() = runBlocking {
+ appOpsController.stub {
+ on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ERRORED)
+ }
+ val controller = AppOpsPermissionController(
+ context = context,
+ app = APP,
+ appOps = AppOps(op = OP),
+ permission = PERMISSION,
+ appOpsController = appOpsController,
+ )
+
+ val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun setAllowed_notSetModeByUid() {
+ val controller = AppOpsPermissionController(
+ context = context,
+ app = APP,
+ appOps = AppOps(op = OP, setModeByUid = false),
+ permission = PERMISSION,
+ )
+
+ controller.setAllowed(true)
+
+ verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, AppOpsManager.MODE_ALLOWED)
+ }
+
+ @Test
+ fun setAllowed_setModeByUid() {
+ val controller = AppOpsPermissionController(
+ context = context,
+ app = APP,
+ appOps = AppOps(op = OP, setModeByUid = true),
+ permission = PERMISSION,
+ )
+
+ controller.setAllowed(true)
+
+ verify(appOpsManager).setUidMode(OP, APP.uid, AppOpsManager.MODE_ALLOWED)
+ }
+
+ private companion object {
+ const val OP = 1
+ const val PERMISSION = "Permission"
+ val APP = ApplicationInfo().apply {
+ packageName = "package.name"
+ uid = 123
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index 07ccdd5..9d12fc7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -26,7 +26,7 @@
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
import com.android.settingslib.spaprivileged.model.app.AppOps
-import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.test.R
import com.google.common.truth.Truth.assertThat
@@ -119,7 +119,7 @@
app = APP,
hasRequestBroaderPermission = false,
hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
@@ -135,7 +135,7 @@
app = APP,
hasRequestBroaderPermission = false,
hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+ appOpsPermissionController = FakeAppOpsPermissionController(true),
)
val isAllowed = getIsAllowed(record)
@@ -144,38 +144,6 @@
}
@Test
- fun isAllowed_defaultAndHasGrantPermission() {
- with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) }
- val record =
- AppOpPermissionRecord(
- app = APP,
- hasRequestBroaderPermission = false,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
-
- val isAllowed = getIsAllowed(record)
-
- assertThat(isAllowed).isTrue()
- }
-
- @Test
- fun isAllowed_defaultAndNotGrantPermission() {
- with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) }
- val record =
- AppOpPermissionRecord(
- app = APP,
- hasRequestBroaderPermission = false,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
-
- val isAllowed = getIsAllowed(record)
-
- assertThat(isAllowed).isFalse()
- }
-
- @Test
fun isAllowed_broaderPermissionTrumps() {
listModel.broaderPermission = BROADER_PERMISSION
with(packageManagers) {
@@ -187,7 +155,7 @@
app = APP,
hasRequestBroaderPermission = true,
hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val isAllowed = getIsAllowed(record)
@@ -202,7 +170,7 @@
app = APP,
hasRequestBroaderPermission = false,
hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val isAllowed = getIsAllowed(record)
@@ -217,7 +185,7 @@
app = APP,
hasRequestBroaderPermission = false,
hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val isChangeable = listModel.isChangeable(record)
@@ -232,7 +200,7 @@
app = NOT_CHANGEABLE_APP,
hasRequestBroaderPermission = false,
hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val isChangeable = listModel.isChangeable(record)
@@ -247,7 +215,7 @@
app = APP,
hasRequestBroaderPermission = false,
hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val isChangeable = listModel.isChangeable(record)
@@ -263,7 +231,7 @@
app = APP,
hasRequestBroaderPermission = true,
hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ appOpsPermissionController = FakeAppOpsPermissionController(false),
)
val isChangeable = listModel.isChangeable(record)
@@ -273,18 +241,18 @@
@Test
fun setAllowed() {
- val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
+ val appOpsPermissionController = FakeAppOpsPermissionController(false)
val record =
AppOpPermissionRecord(
app = APP,
hasRequestBroaderPermission = false,
hasRequestPermission = true,
- appOpsController = appOpsController,
+ appOpsPermissionController = appOpsPermissionController,
)
listModel.setAllowed(record = record, newAllowed = true)
- assertThat(appOpsController.setAllowedCalledWith).isTrue()
+ assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue()
}
private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
@@ -314,14 +282,12 @@
}
}
-private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
+private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController {
var setAllowedCalledWith: Boolean? = null
- override val modeFlow = flowOf(fakeMode)
+ override val isAllowedFlow = flowOf(allowed)
override fun setAllowed(allowed: Boolean) {
setAllowedCalledWith = allowed
}
-
- override fun getMode() = fakeMode
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 9c0d29d..32557b9 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -69,3 +69,13 @@
description: "Allow all widgets on the lock screen by default."
bug: "328261690"
}
+
+flag {
+ name: "enable_determining_advanced_details_header_with_metadata"
+ namespace: "pixel_cross_device_control"
+ description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header."
+ bug: "328556903"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index 7aae1a6..6f614b3 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -31,3 +31,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "use_playback_info_for_routing_controls"
+ namespace: "media_solutions"
+ description: "Use app-provided playback info when providing media routing information."
+ bug: "333564788"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 8917412..721e7b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -276,6 +276,14 @@
if (isUntetheredHeadset(bluetoothDevice)) {
return true;
}
+ if (Flags.enableDeterminingAdvancedDetailsHeaderWithMetadata()) {
+ // A FastPair device that use advanced details header must have METADATA_MAIN_ICON
+ if (getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON) != null) {
+ Log.d(TAG, "isAdvancedDetailsHeader is true with main icon uri");
+ return true;
+ }
+ return false;
+ }
// The metadata is for Android S
String deviceType =
getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
@@ -302,12 +310,15 @@
if (isUntetheredHeadset(bluetoothDevice)) {
return true;
}
- // The metadata is for Android S
- String deviceType =
- getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
- if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
- Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
- return true;
+ if (!Flags.enableDeterminingAdvancedDetailsHeaderWithMetadata()) {
+ // The METADATA_IS_UNTETHERED_HEADSET of an untethered FastPair headset is always true,
+ // so there's no need to check the device type.
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
+ if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
+ Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device");
+ return true;
+ }
}
return false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index eae58ad..b7758de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -40,6 +40,7 @@
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.session.MediaController.PlaybackInfo;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
@@ -51,6 +52,8 @@
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -135,19 +138,28 @@
@NonNull protected final UserHandle mUserHandle;
private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private MediaDevice mCurrentConnectedDevice;
+ private MediaController mMediaController;
+ private PlaybackInfo mLastKnownPlaybackInfo;
private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
+ private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
+
/* package */ InfoMediaManager(
@NonNull Context context,
@NonNull String packageName,
@NonNull UserHandle userHandle,
- @NonNull LocalBluetoothManager localBluetoothManager) {
+ @NonNull LocalBluetoothManager localBluetoothManager,
+ @Nullable MediaController mediaController) {
mContext = context;
mBluetoothManager = localBluetoothManager;
mPackageName = packageName;
mUserHandle = userHandle;
+ mMediaController = mediaController;
+ if (mediaController != null) {
+ mLastKnownPlaybackInfo = mediaController.getPlaybackInfo();
+ }
}
/**
@@ -159,12 +171,19 @@
* speakers, as opposed to app-specific routing (for example, casting to another device).
* @param userHandle The {@link UserHandle} of the user on which the app to control is running,
* or null if the caller does not need app-specific routing (see {@code packageName}).
+ * @param token The token of the associated {@link MediaSession} for which to do media routing.
*/
public static InfoMediaManager createInstance(
Context context,
@Nullable String packageName,
@Nullable UserHandle userHandle,
- LocalBluetoothManager localBluetoothManager) {
+ LocalBluetoothManager localBluetoothManager,
+ @Nullable MediaSession.Token token) {
+ MediaController mediaController = null;
+
+ if (Flags.usePlaybackInfoForRoutingControls() && token != null) {
+ mediaController = new MediaController(context, token);
+ }
// The caller is only interested in system routes (headsets, built-in speakers, etc), and is
// not interested in a specific app's routing. The media routing APIs still require a
@@ -180,16 +199,16 @@
if (Flags.useMediaRouter2ForInfoMediaManager()) {
try {
return new RouterInfoMediaManager(
- context, packageName, userHandle, localBluetoothManager);
+ context, packageName, userHandle, localBluetoothManager, mediaController);
} catch (PackageNotAvailableException ex) {
// TODO: b/293578081 - Propagate this exception to callers for proper handling.
Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
return new NoOpInfoMediaManager(
- context, packageName, userHandle, localBluetoothManager);
+ context, packageName, userHandle, localBluetoothManager, mediaController);
}
} else {
return new ManagerInfoMediaManager(
- context, packageName, userHandle, localBluetoothManager);
+ context, packageName, userHandle, localBluetoothManager, mediaController);
}
}
@@ -310,6 +329,9 @@
if (wasEmpty) {
mMediaDevices.clear();
registerRouter();
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mMediaControllerCallback);
+ }
updateRouteListingPreference();
refreshDevices();
}
@@ -323,6 +345,9 @@
*/
public final void unregisterCallback(@NonNull MediaDeviceCallback callback) {
if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mMediaControllerCallback);
+ }
unregisterRouter();
}
}
@@ -389,7 +414,34 @@
private RoutingSessionInfo getActiveRoutingSession() {
// List is never empty.
final List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage();
- return sessions.get(sessions.size() - 1);
+ RoutingSessionInfo activeSession = sessions.get(sessions.size() - 1);
+
+ // Logic from MediaRouter2Manager#getRoutingSessionForMediaController
+ if (!Flags.usePlaybackInfoForRoutingControls() || mMediaController == null) {
+ return activeSession;
+ }
+
+ PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo();
+ if (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+ // Return system session.
+ return sessions.get(0);
+ }
+
+ // For PLAYBACK_TYPE_REMOTE.
+ String volumeControlId = playbackInfo.getVolumeControlId();
+ for (RoutingSessionInfo session : sessions) {
+ if (TextUtils.equals(volumeControlId, session.getId())) {
+ return session;
+ }
+ // Workaround for provider not being able to know the unique session ID.
+ if (TextUtils.equals(volumeControlId, session.getOriginalId())
+ && TextUtils.equals(
+ mMediaController.getPackageName(), session.getOwnerPackageName())) {
+ return session;
+ }
+ }
+
+ return activeSession;
}
boolean isRoutingSessionAvailableForVolumeControl() {
@@ -808,4 +860,23 @@
}
}
}
+
+ private final class MediaControllerCallback extends MediaController.Callback {
+ @Override
+ public void onSessionDestroyed() {
+ mMediaController = null;
+ refreshDevices();
+ }
+
+ @Override
+ public void onAudioInfoChanged(@NonNull PlaybackInfo info) {
+ if (info.getPlaybackType() != mLastKnownPlaybackInfo.getPlaybackType()
+ || !TextUtils.equals(
+ info.getVolumeControlId(),
+ mLastKnownPlaybackInfo.getVolumeControlId())) {
+ refreshDevices();
+ }
+ mLastKnownPlaybackInfo = info;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 473c627..cfa825b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -149,7 +149,11 @@
// TODO: b/321969740 - Take the userHandle as a parameter and pass it through. The
// package name is not sufficient to unambiguously identify an app.
InfoMediaManager.createInstance(
- context, packageName, /* userHandle */ null, mLocalBluetoothManager);
+ context,
+ packageName,
+ /* userHandle */ null,
+ mLocalBluetoothManager,
+ /* token */ null);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index d621751..82b1976 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -21,6 +21,7 @@
import android.media.MediaRouter2Manager;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -55,8 +56,9 @@
Context context,
@NonNull String packageName,
@NonNull UserHandle userHandle,
- LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, userHandle, localBluetoothManager);
+ LocalBluetoothManager localBluetoothManager,
+ @Nullable MediaController mediaController) {
+ super(context, packageName, userHandle, localBluetoothManager, mediaController);
mRouterManager = MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index d2b018c..2c7ec93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -20,6 +20,7 @@
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -60,8 +61,9 @@
Context context,
@NonNull String packageName,
@NonNull UserHandle userHandle,
- LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, userHandle, localBluetoothManager);
+ LocalBluetoothManager localBluetoothManager,
+ @Nullable MediaController mediaController) {
+ super(context, packageName, userHandle, localBluetoothManager, mediaController);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 045c60d..6571dd7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -25,6 +25,7 @@
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -71,9 +72,10 @@
Context context,
@NonNull String packageName,
@NonNull UserHandle userHandle,
- LocalBluetoothManager localBluetoothManager)
+ LocalBluetoothManager localBluetoothManager,
+ @Nullable MediaController mediaController)
throws PackageNotAvailableException {
- super(context, packageName, userHandle, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager, mediaController);
MediaRouter2 router = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index 23b2cc2..89f3cf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -278,7 +278,7 @@
}
@Override
- public void onAudioInfoChanged(PlaybackInfo info) {
+ public void onAudioInfoChanged(@NonNull PlaybackInfo info) {
if (D.BUG) {
Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+ " sentRemote=" + sentRemote);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index 3bd37a2..a2ee2ec 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -65,7 +65,7 @@
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
InfoMediaManager manager =
InfoMediaManager.createInstance(
- mContext, mContext.getPackageName(), mContext.getUser(), null);
+ mContext, mContext.getPackageName(), mContext.getUser(), null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -73,14 +73,15 @@
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+ InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null, null);
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+ InfoMediaManager manager =
+ InfoMediaManager.createInstance(mContext, null, null, null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -89,7 +90,7 @@
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
InfoMediaManager manager =
InfoMediaManager.createInstance(
- mContext, mContext.getPackageName(), mContext.getUser(), null);
+ mContext, mContext.getPackageName(), mContext.getUser(), null, null);
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 7a2818d..a638df5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -15,6 +15,8 @@
*/
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -33,11 +35,13 @@
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Pair;
import com.android.settingslib.widget.AdaptiveIcon;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -83,11 +87,15 @@
private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
@@ -253,6 +261,25 @@
}
@Test
+ public void isAdvancedDetailsHeader_noMainIcon_returnFalse() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
+
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void isAdvancedDetailsHeader_hasMainIcon_returnTrue() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON))
+ .thenReturn(STRING_METADATA.getBytes());
+
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
@@ -294,6 +321,18 @@
}
@Test
+ public void isAdvancedUntetheredDevice_untetheredHeadsetMetadataIsFalse_returnFalse() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn("false".getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() {
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 69faddf..ce07fe9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -146,7 +146,11 @@
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
new ManagerInfoMediaManager(
- mContext, TEST_PACKAGE_NAME, mContext.getUser(), mLocalBluetoothManager);
+ mContext,
+ TEST_PACKAGE_NAME,
+ mContext.getUser(),
+ mLocalBluetoothManager,
+ /* mediaController */ null);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index ddb5419..12541bb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -117,9 +117,16 @@
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
// Need to call constructor to initialize final fields.
- mInfoMediaManager = mock(
- InfoMediaManager.class,
- withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+ mInfoMediaManager =
+ mock(
+ InfoMediaManager.class,
+ withSettings()
+ .useConstructor(
+ mContext,
+ TEST_PACKAGE_NAME,
+ android.os.Process.myUserHandle(),
+ mLocalBluetoothManager,
+ /* mediaController */ null));
doReturn(
List.of(
new RoutingSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
index 908f50d..c566741 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -47,7 +47,8 @@
mContext,
/* packageName */ "FAKE_PACKAGE_NAME",
mContext.getUser(),
- /* localBluetoothManager */ null);
+ /* localBluetoothManager */ null,
+ /* mediaController */ null);
}
@Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 8f8445d..63e98de 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -38,6 +38,8 @@
* NOTE: All settings which are backed up should have a corresponding validator.
*/
public static final String[] SETTINGS_TO_BACKUP = {
+ Settings.Global.CONNECTED_APPS_ALLOWED_PACKAGES,
+ Settings.Global.CONNECTED_APPS_DISALLOWED_PACKAGES,
Settings.Global.APPLY_RAMPING_RINGER,
Settings.Global.BUGREPORT_IN_POWER_MENU, // moved to secure
Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c274534..bc0ee7f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -51,6 +51,9 @@
public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
static {
+ VALIDATORS.put(Global.CONNECTED_APPS_ALLOWED_PACKAGES, new PackageNameListValidator((",")));
+ VALIDATORS.put(Global.CONNECTED_APPS_DISALLOWED_PACKAGES,
+ new PackageNameListValidator((",")));
VALIDATORS.put(Global.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.BUGREPORT_IN_POWER_MENU, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 4579168..050a370 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -200,7 +200,7 @@
mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2));
return null;
}).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyBoolean());
setWarningState(mContext, STATE_HIDE);
@@ -543,7 +543,7 @@
getInstrumentation().waitForIdleSync();
verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(),
- anyInt(), anyInt(), any(), anyBoolean());
+ anyInt(), anyInt(), any(), anyBoolean(), anyBoolean());
sendBugreportFinished();
}
@@ -608,7 +608,7 @@
ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass(
IDumpstateListener.class);
verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(),
- anyInt(), anyInt(), listenerCap.capture(), anyBoolean());
+ anyInt(), anyInt(), listenerCap.capture(), anyBoolean(), anyBoolean());
mIDumpstateListener = listenerCap.getValue();
assertNotNull("Dumpstate listener should not be null", mIDumpstateListener);
mIDumpstateListener.onProgress(0);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8b60ed0..c4929a1 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -766,6 +766,7 @@
],
static_libs: [
"RoboTestLibraries",
+ "mockito-kotlin2",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 15df1be..76ffc8b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -68,13 +68,17 @@
@Composable
private fun Content(dialog: SystemUIDialog) {
val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle(true)
-
if (!isAvailable) {
SideEffect { dialog.dismiss() }
return
}
val slice by viewModel.popupSlice.collectAsStateWithLifecycle()
+ if (!viewModel.isClickable(slice)) {
+ SideEffect { dialog.dismiss() }
+ return
+ }
+
SliceAndroidView(
modifier = Modifier.fillMaxWidth(),
slice = slice,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index f61ddeb..68fbd1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -20,9 +20,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -30,14 +36,16 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
@ExperimentalCoroutinesApi
@@ -65,9 +73,10 @@
fun isLongPressEnabled_udfpsRunning() =
testScope.runTest {
val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
- fingerprintPropertyRepository.supportsUdfps()
- fingerprintAuthRepository.setIsRunning(true)
- keyguardRepository.setKeyguardDismissible(false)
+ setUpState(
+ isUdfpsSupported = true,
+ isUdfpsRunning = true,
+ )
assertThat(isLongPressEnabled).isFalse()
}
@@ -75,10 +84,10 @@
fun isLongPressEnabled_unlocked() =
testScope.runTest {
val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
- fingerprintPropertyRepository.supportsUdfps()
- keyguardRepository.setKeyguardDismissible(true)
- advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
- runCurrent()
+ setUpState(
+ isUdfpsSupported = true,
+ isLockscreenDismissible = true,
+ )
assertThat(isLongPressEnabled).isTrue()
}
@@ -86,10 +95,9 @@
fun isLongPressEnabled_lock() =
testScope.runTest {
val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
- keyguardRepository.setKeyguardDismissible(false)
+ setUpState(isUdfpsSupported = true)
// udfps supported
- fingerprintPropertyRepository.supportsUdfps()
assertThat(isLongPressEnabled).isTrue()
// udfps isn't supported
@@ -112,42 +120,90 @@
}
@Test
+ @DisableSceneContainer
fun iconType_fingerprint() =
testScope.runTest {
val iconType by collectLastValue(underTest.iconType)
- keyguardRepository.setKeyguardDismissible(false)
- fingerprintPropertyRepository.supportsUdfps()
- fingerprintAuthRepository.setIsRunning(true)
+ setUpState(
+ isUdfpsSupported = true,
+ isUdfpsRunning = true,
+ )
assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT)
}
@Test
+ @DisableSceneContainer
fun iconType_locked() =
testScope.runTest {
val iconType by collectLastValue(underTest.iconType)
- keyguardRepository.setKeyguardDismissible(false)
- fingerprintAuthRepository.setIsRunning(false)
+ setUpState()
assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK)
}
@Test
+ @DisableSceneContainer
fun iconType_unlocked() =
testScope.runTest {
val iconType by collectLastValue(underTest.iconType)
- keyguardRepository.setKeyguardDismissible(true)
- advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
- fingerprintAuthRepository.setIsRunning(false)
+ setUpState(isLockscreenDismissible = true)
assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
}
@Test
+ @DisableSceneContainer
fun iconType_none() =
testScope.runTest {
val iconType by collectLastValue(underTest.iconType)
- keyguardRepository.setKeyguardDismissible(true)
- advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
- fingerprintPropertyRepository.supportsUdfps()
- fingerprintAuthRepository.setIsRunning(true)
+ setUpState(
+ isUdfpsSupported = true,
+ isUdfpsRunning = true,
+ isLockscreenDismissible = true,
+ )
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun iconType_fingerprint_withSceneContainer() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ setUpState(
+ isUdfpsSupported = true,
+ isUdfpsRunning = true,
+ )
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun iconType_locked_withSceneContainer() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ setUpState()
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun iconType_unlocked_withSceneContainer() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ setUpState(
+ isLockscreenDismissible = true,
+ )
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun iconType_none_withSceneContainer() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ setUpState(
+ isUdfpsSupported = true,
+ isUdfpsRunning = true,
+ isLockscreenDismissible = true,
+ )
assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
}
@@ -166,14 +222,12 @@
kosmos.fakeAccessibilityRepository.isEnabled.value = true
// interactive lock icon
- keyguardRepository.setKeyguardDismissible(false)
- fingerprintPropertyRepository.supportsUdfps()
+ setUpState(isUdfpsSupported = true)
assertThat(accessibilityDelegateHint)
.isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE)
// non-interactive lock icon
- keyguardRepository.setKeyguardDismissible(false)
fingerprintPropertyRepository.supportsRearFps()
assertThat(accessibilityDelegateHint)
@@ -187,10 +241,10 @@
kosmos.fakeAccessibilityRepository.isEnabled.value = true
// interactive unlock icon
- keyguardRepository.setKeyguardDismissible(true)
- fingerprintPropertyRepository.supportsUdfps()
- advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
- runCurrent()
+ setUpState(
+ isUdfpsSupported = true,
+ isLockscreenDismissible = true,
+ )
assertThat(accessibilityDelegateHint)
.isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER)
@@ -199,4 +253,45 @@
private fun deviceEntryIconTransitionAlpha(alpha: Float) {
deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
}
+
+ private suspend fun TestScope.setUpState(
+ isUdfpsSupported: Boolean = false,
+ isUdfpsRunning: Boolean = false,
+ isLockscreenDismissible: Boolean = false,
+ ) {
+ if (isUdfpsSupported) {
+ fingerprintPropertyRepository.supportsUdfps()
+ }
+ if (isUdfpsRunning) {
+ check(isUdfpsSupported) { "Cannot set UDFPS as running if it's not supported!" }
+ fingerprintAuthRepository.setIsRunning(true)
+ } else {
+ fingerprintAuthRepository.setIsRunning(false)
+ }
+ if (isLockscreenDismissible) {
+ setLockscreenDismissible()
+ } else {
+ if (!SceneContainerFlag.isEnabled) {
+ keyguardRepository.setKeyguardDismissible(false)
+ }
+ }
+ runCurrent()
+ }
+
+ private suspend fun TestScope.setLockscreenDismissible() {
+ if (SceneContainerFlag.isEnabled) {
+ // Need to set up a collection for the authentication to be propagated.
+ val unused by collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+ runCurrent()
+ assertThat(
+ kosmos.authenticationInteractor.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN
+ )
+ )
+ .isEqualTo(AuthenticationResult.SUCCEEDED)
+ } else {
+ keyguardRepository.setKeyguardDismissible(true)
+ }
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index 365a7c3..856c3fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -195,6 +195,7 @@
eq(PACKAGE_NAME),
eq(true),
eq(dialogTransitionController),
+ eq(null),
eq(null)
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index bba9991..8b4265f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -23,8 +23,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
@@ -41,9 +47,19 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
class HeadsUpNotificationInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ fakeKeyguardTransitionRepository =
+ FakeKeyguardTransitionRepository(initInLockscreen = false)
+ }
private val testScope = kosmos.testScope
- private val repository = kosmos.headsUpNotificationRepository
+ private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+ private val headsUpRepository by lazy { kosmos.headsUpNotificationRepository }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val keyguardViewStateRepository by lazy {
+ kosmos.notificationsKeyguardViewStateRepository
+ }
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val underTest = kosmos.headsUpNotificationInteractor
@@ -60,7 +76,7 @@
testScope.runTest {
val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
// WHEN no pinned rows are set
- repository.setNotifications(
+ headsUpRepository.setNotifications(
fakeHeadsUpRowRepository("key 0"),
fakeHeadsUpRowRepository("key 1"),
fakeHeadsUpRowRepository("key 2"),
@@ -76,7 +92,7 @@
testScope.runTest {
val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
// WHEN a pinned rows is set
- repository.setNotifications(
+ headsUpRepository.setNotifications(
fakeHeadsUpRowRepository("key 0", isPinned = true),
fakeHeadsUpRowRepository("key 1"),
fakeHeadsUpRowRepository("key 2"),
@@ -98,7 +114,7 @@
fakeHeadsUpRowRepository("key 1"),
fakeHeadsUpRowRepository("key 2"),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
// WHEN a row gets pinned
@@ -120,7 +136,7 @@
fakeHeadsUpRowRepository("key 1"),
fakeHeadsUpRowRepository("key 2"),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
// THEN that row gets unpinned
@@ -144,7 +160,7 @@
testScope.runTest {
val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
// WHEN no rows are pinned
- repository.setNotifications(
+ headsUpRepository.setNotifications(
fakeHeadsUpRowRepository("key 0"),
fakeHeadsUpRowRepository("key 1"),
fakeHeadsUpRowRepository("key 2"),
@@ -166,7 +182,7 @@
fakeHeadsUpRowRepository("key 1", isPinned = true),
fakeHeadsUpRowRepository("key 2"),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
// THEN the unpinned rows are filtered
@@ -184,7 +200,7 @@
fakeHeadsUpRowRepository("key 1", isPinned = true),
fakeHeadsUpRowRepository("key 2"),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
// WHEN all rows gets pinned
@@ -206,7 +222,7 @@
fakeHeadsUpRowRepository("key 1", isPinned = true),
fakeHeadsUpRowRepository("key 2", isPinned = true),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
// THEN no rows are filtered
@@ -224,7 +240,7 @@
fakeHeadsUpRowRepository("key 1", isPinned = true),
fakeHeadsUpRowRepository("key 2", isPinned = true),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
// WHEN a row gets unpinned
@@ -246,7 +262,7 @@
fakeHeadsUpRowRepository("key 1"),
fakeHeadsUpRowRepository("key 2"),
)
- repository.setNotifications(rows)
+ headsUpRepository.setNotifications(rows)
runCurrent()
rows[0].isPinned.value = true
@@ -262,6 +278,96 @@
assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
}
+ @Test
+ fun showHeadsUpStatusBar_true() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+
+ assertThat(showHeadsUpStatusBar).isTrue()
+ }
+
+ @Test
+ fun showHeadsUpStatusBar_withoutPinnedNotifications_false() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN no row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = false))
+
+ assertThat(showHeadsUpStatusBar).isFalse()
+ }
+
+ @Test
+ fun showHeadsUpStatusBar_whenShadeExpanded_false() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+ // AND the shade is expanded
+ shadeTestUtil.setShadeExpansion(1.0f)
+
+ assertThat(showHeadsUpStatusBar).isFalse()
+ }
+
+ @Test
+ fun showHeadsUpStatusBar_notificationsAreHidden_false() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+ // AND the notifications are hidden
+ keyguardViewStateRepository.areNotificationsFullyHidden.value = true
+
+ assertThat(showHeadsUpStatusBar).isFalse()
+ }
+
+ @Test
+ fun showHeadsUpStatusBar_onLockScreen_false() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+ // AND the lock screen is shown
+ keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+
+ assertThat(showHeadsUpStatusBar).isFalse()
+ }
+
+ @Test
+ fun showHeadsUpStatusBar_onByPassLockScreen_true() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN a row is pinned
+ headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+ // AND the lock screen is shown
+ keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ // AND bypass is enabled
+ faceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(showHeadsUpStatusBar).isTrue()
+ }
+
+ @Test
+ fun showHeadsUpStatusBar_onByPassLockScreen_withoutNotifications_false() =
+ testScope.runTest {
+ val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+ // WHEN no pinned rows
+ // AND the lock screen is shown
+ keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ // AND bypass is enabled
+ faceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(showHeadsUpStatusBar).isFalse()
+ }
+
private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
this.isPinned.value = isPinned
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index cc5df74..9fde116 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -524,9 +524,9 @@
// WHEN there are no pinned rows
val rows =
arrayListOf(
- fakeHeadsUpRowRepository(key = "0"),
- fakeHeadsUpRowRepository(key = "1"),
- fakeHeadsUpRowRepository(key = "2"),
+ FakeHeadsUpRowRepository(key = "0"),
+ FakeHeadsUpRowRepository(key = "1"),
+ FakeHeadsUpRowRepository(key = "2"),
)
headsUpRepository.setNotifications(
rows,
@@ -565,8 +565,8 @@
val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
headsUpRepository.setNotifications(
- fakeHeadsUpRowRepository(key = "0", isPinned = true),
- fakeHeadsUpRowRepository(key = "1")
+ FakeHeadsUpRowRepository(key = "0", isPinned = true),
+ FakeHeadsUpRowRepository(key = "1")
)
runCurrent()
@@ -580,8 +580,8 @@
val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
headsUpRepository.setNotifications(
- fakeHeadsUpRowRepository(key = "0"),
- fakeHeadsUpRowRepository(key = "1"),
+ FakeHeadsUpRowRepository(key = "0"),
+ FakeHeadsUpRowRepository(key = "1"),
)
runCurrent()
@@ -607,7 +607,7 @@
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
shadeTestUtil.setQsExpansion(0.0f)
- fakeKeyguardRepository.setKeyguardShowing(false)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
runCurrent()
assertThat(animationsEnabled).isTrue()
@@ -620,14 +620,9 @@
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
shadeTestUtil.setQsExpansion(0.0f)
- fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
assertThat(animationsEnabled).isFalse()
}
-
- private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
- FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
- this.isPinned.value = isPinned
- }
}
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
index bb8cece..ad6c154 100644
--- a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -14,10 +14,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape
+<inset
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
- <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
-</shape>
+ android:insetLeft="@dimen/overlay_action_container_minimum_edge_spacing"
+ android:insetRight="@dimen/overlay_action_container_minimum_edge_spacing">
+ <shape
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+ </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
index 521369e..65005f8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay2.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml
@@ -24,6 +24,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/clipboard_overlay_window_name">
+ <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
+ this we'd need to express margins as the sum of two different dimens). -->
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/min_edge_guideline"
+ app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
+ android:orientation="vertical"/>
+ <!-- Negative horizontal margin because this container background must render beyond the thing
+ it's constrained by (the actions themselves). -->
<FrameLayout
android:id="@+id/actions_container_background"
android:visibility="gone"
@@ -31,11 +41,12 @@
android:layout_width="0dp"
android:elevation="4dp"
android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
+ android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
+ android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
+ app:layout_constraintTop_toTopOf="@id/actions_container"
+ app:layout_constraintEnd_toEndOf="@id/actions_container"
app:layout_constraintBottom_toBottomOf="parent"/>
<HorizontalScrollView
android:id="@+id/actions_container"
@@ -76,7 +87,7 @@
android:layout_marginBottom="@dimen/overlay_preview_container_margin"
android:elevation="7dp"
android:background="@drawable/overlay_border"
- app:layout_constraintStart_toStartOf="@id/actions_container_background"
+ app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
app:layout_constraintTop_toTopOf="@id/clipboard_preview"
app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 6b65e9c..796def3 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -79,7 +79,6 @@
android:layout_width="wrap_content"
android:elevation="4dp"
android:background="@drawable/shelf_action_chip_container_background"
- android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/guideline"
>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b960813..02b74ce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -449,8 +449,12 @@
<dimen name="overlay_preview_container_margin">8dp</dimen>
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
<dimen name="overlay_action_container_margin_bottom">6dp</dimen>
- <!-- minimum distance to the left, right or bottom edges. -->
+ <!--
+ minimum distance to the left, right or bottom edges. Keep in sync with
+ negative_overlay_action_container_minimum_edge_spacing. -->
<dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen>
+ <!-- Keep in sync with overlay_action_container_minimum_edge_spacing -->
+ <dimen name="negative_overlay_action_container_minimum_edge_spacing">-12dp</dimen>
<dimen name="overlay_bg_protection_height">242dp</dimen>
<dimen name="overlay_action_container_corner_radius">20dp</dimen>
<dimen name="overlay_action_container_padding_vertical">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 207f7db..f320057 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -221,7 +221,8 @@
(view) -> {
// TODO: b/321969740 - Take the userHandle as a parameter and pass it through.
// The package name is not sufficient to unambiguously identify an app.
- mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null, null);
+ mMediaOutputDialogManager.createAndShow(
+ mOutputPackageName, true, null, null, null);
dialog.dismiss();
});
cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index a32b2aa..6ca8eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -36,6 +36,9 @@
val authenticated: Flow<Boolean>
+ /** Whether bypass is enabled. If enabled, face unlock dismisses the lock screen. */
+ val isBypassEnabled: Flow<Boolean>
+
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 6629f6e..9486798 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -22,6 +22,7 @@
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flowOf
/**
* Implementation of the interactor that noops all face auth operations.
@@ -35,6 +36,7 @@
override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
override val lockedOut: Flow<Boolean> = emptyFlow()
override val authenticated: Flow<Boolean> = emptyFlow()
+ override val isBypassEnabled: Flow<Boolean> = flowOf(false)
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 669cd94..87f3f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -286,6 +286,7 @@
override val detectionStatus = repository.detectionStatus
override val lockedOut: Flow<Boolean> = repository.isLockedOut
override val authenticated: Flow<Boolean> = repository.isAuthenticated
+ override val isBypassEnabled: Flow<Boolean> = repository.isBypassEnabled
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 8d90933..fa43ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -207,19 +207,24 @@
.distinctUntilChanged()
private val isUnlocked: Flow<Boolean> =
- keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
- if (!isUnlocked) {
- flowOf(false)
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.isUnlocked
} else {
- flow {
- // delay in case device ends up transitioning away from the lock screen;
- // we don't want to animate to the unlocked icon and just let the
- // icon fade with the transition to GONE
- delay(UNLOCKED_DELAY_MS)
- emit(true)
+ keyguardInteractor.isKeyguardDismissible
+ }
+ .flatMapLatest { isUnlocked ->
+ if (!isUnlocked) {
+ flowOf(false)
+ } else {
+ flow {
+ // delay in case device ends up transitioning away from the lock screen;
+ // we don't want to animate to the unlocked icon and just let the
+ // icon fade with the transition to GONE
+ delay(UNLOCKED_DELAY_MS)
+ emit(true)
+ }
}
}
- }
val iconType: Flow<DeviceEntryIconView.IconType> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 043fbfa..486d4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -102,7 +102,8 @@
return
}
val controller = data.token?.let { controllerFactory.create(it) }
- val localMediaManager = localMediaManagerFactory.create(data.packageName)
+ val localMediaManager =
+ localMediaManagerFactory.create(data.packageName, controller?.sessionToken)
val muteAwaitConnectionManager =
muteAwaitConnectionManagerFactory.create(localMediaManager)
entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager)
@@ -224,9 +225,9 @@
}
@WorkerThread
- override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
- val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
- val newPlaybackVolumeControlId = info?.volumeControlId
+ override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
+ val newPlaybackType = info.playbackType
+ val newPlaybackVolumeControlId = info.volumeControlId
if (
newPlaybackType == playbackType &&
newPlaybackVolumeControlId == playbackVolumeControlId
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 1a0f582..3f75938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -155,11 +155,16 @@
return false
}
- fun startMediaOutputDialog(expandable: Expandable, packageName: String) {
+ fun startMediaOutputDialog(
+ expandable: Expandable,
+ packageName: String,
+ token: MediaSession.Token? = null
+ ) {
mediaOutputDialogManager.createAndShowWithController(
packageName,
true,
- expandable.dialogController()
+ expandable.dialogController(),
+ token = token,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 0bc3c439..5ec4f88 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -743,7 +743,8 @@
mPackageName,
/* aboveStatusBar */ true,
mMediaViewHolder.getSeamlessButton(),
- UserHandle.getUserHandleForUid(mUid));
+ UserHandle.getUserHandleForUid(mUid),
+ mToken);
}
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
@@ -775,7 +776,8 @@
mPackageName,
/* aboveStatusBar */ true,
mMediaViewHolder.getSeamlessButton(),
- UserHandle.getUserHandleForUid(mUid));
+ UserHandle.getUserHandleForUid(mUid),
+ mToken);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 1944f07..099991d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -231,12 +231,20 @@
)
} else {
logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId)
- interactor.startMediaOutputDialog(expandable, model.packageName)
+ interactor.startMediaOutputDialog(
+ expandable,
+ model.packageName,
+ model.token
+ )
}
} else {
logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId)
device?.intent?.let { interactor.startDeviceIntent(it) }
- ?: interactor.startMediaOutputDialog(expandable, model.packageName)
+ ?: interactor.startMediaOutputDialog(
+ expandable,
+ model.packageName,
+ model.token
+ )
}
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index ff8e903b..0a717ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.util
import android.content.Context
+import android.media.session.MediaSession
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.LocalMediaManager
@@ -30,10 +31,16 @@
private val localBluetoothManager: LocalBluetoothManager?
) {
/** Creates a [LocalMediaManager] for the given package. */
- fun create(packageName: String?): LocalMediaManager {
+ fun create(packageName: String?, token: MediaSession.Token? = null): LocalMediaManager {
// TODO: b/321969740 - Populate the userHandle parameter in InfoMediaManager. The user
// handle is necessary to disambiguate the same package running on different users.
- return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+ return InfoMediaManager.createInstance(
+ context,
+ packageName,
+ null,
+ localBluetoothManager,
+ token
+ )
.run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 06267e2..6ef9ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -40,7 +40,12 @@
// TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
// disambiguate the same package running on different users.
- val controller = mediaOutputControllerFactory.create(packageName, /* userHandle= */ null)
+ val controller =
+ mediaOutputControllerFactory.create(
+ packageName,
+ /* userHandle= */ null,
+ /* token */ null,
+ )
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index d6ca320..c2cfdbe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -78,6 +78,7 @@
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.media.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -141,6 +142,7 @@
private final KeyguardManager mKeyGuardManager;
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
+ private final MediaSession.Token mToken;
@VisibleForTesting
boolean mIsRefreshing = false;
@@ -179,6 +181,7 @@
Context context,
@Assisted String packageName,
@Assisted @Nullable UserHandle userHandle,
+ @Assisted @Nullable MediaSession.Token token,
MediaSessionManager mediaSessionManager,
@Nullable LocalBluetoothManager lbm,
ActivityStarter starter,
@@ -202,8 +205,9 @@
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
+ mToken = token;
InfoMediaManager imm =
- InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm);
+ InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -235,7 +239,8 @@
@AssistedFactory
public interface Factory {
/** Construct a MediaOutputController */
- MediaOutputController create(String packageName, UserHandle userHandle);
+ MediaOutputController create(
+ String packageName, UserHandle userHandle, MediaSession.Token token);
}
protected void start(@NonNull Callback cb) {
@@ -297,23 +302,28 @@
}
private MediaController getMediaController() {
- for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
- final Notification notification = entry.getSbn().getNotification();
- if (notification.isMediaNotification()
- && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
- MediaSession.Token token = notification.extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token.class);
- return new MediaController(mContext, token);
+ if (mToken != null && Flags.usePlaybackInfoForRoutingControls()) {
+ return new MediaController(mContext, mToken);
+ } else {
+ for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.isMediaNotification()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ MediaSession.Token token =
+ notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class);
+ return new MediaController(mContext, token);
+ }
}
- }
- for (MediaController controller : mMediaSessionManager.getActiveSessionsForUser(null,
- mUserTracker.getUserHandle())) {
- if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
- return controller;
+ for (MediaController controller :
+ mMediaSessionManager.getActiveSessionsForUser(
+ null, mUserTracker.getUserHandle())) {
+ if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
+ return controller;
+ }
}
+ return null;
}
- return null;
}
@Override
@@ -869,10 +879,6 @@
mMetricLogger.logInteractionUnmute(device);
}
- String getPackageName() {
- return mPackageName;
- }
-
boolean hasAdjustVolumeUserRestriction() {
if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) {
@@ -955,6 +961,7 @@
mContext,
mPackageName,
mUserHandle,
+ mToken,
mMediaSessionManager,
mLocalBluetoothManager,
mActivityStarter,
@@ -1060,7 +1067,7 @@
boolean isBroadcastSupported() {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
- return broadcast != null ? true : false;
+ return broadcast != null;
}
boolean isBluetoothLeBroadcastEnabled() {
@@ -1194,13 +1201,6 @@
assistant.unregisterServiceCallBack(callback);
}
- private boolean isPlayBackInfoLocal() {
- return mMediaController != null
- && mMediaController.getPlaybackInfo() != null
- && mMediaController.getPlaybackInfo().getPlaybackType()
- == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
- }
-
boolean isPlaying() {
if (mMediaController == null) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 04d1492..ee816942 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.dialog
import android.content.Context
+import android.media.session.MediaSession
import android.os.UserHandle
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
@@ -49,7 +50,8 @@
packageName: String,
aboveStatusBar: Boolean,
view: View? = null,
- userHandle: UserHandle? = null
+ userHandle: UserHandle? = null,
+ token: MediaSession.Token? = null
) {
createAndShowWithController(
packageName,
@@ -65,6 +67,7 @@
)
},
userHandle = userHandle,
+ token = token,
)
}
@@ -77,6 +80,7 @@
aboveStatusBar: Boolean,
controller: DialogTransitionAnimator.Controller?,
userHandle: UserHandle? = null,
+ token: MediaSession.Token? = null,
) {
createAndShow(
packageName,
@@ -84,6 +88,7 @@
dialogTransitionAnimatorController = controller,
includePlaybackAndAppMetadata = true,
userHandle = userHandle,
+ token = token,
)
}
@@ -108,11 +113,12 @@
dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
includePlaybackAndAppMetadata: Boolean = true,
userHandle: UserHandle? = null,
+ token: MediaSession.Token? = null,
) {
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName, userHandle)
+ val controller = mediaOutputControllerFactory.create(packageName, userHandle, token)
val mediaOutputDialog =
MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index 9cc2888..846460e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -56,7 +56,11 @@
public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
if (!TextUtils.isEmpty(packageName)) {
mMediaOutputDialogManager.createAndShow(
- packageName, /* aboveStatusBar= */ false, /* view= */ null, userHandle);
+ packageName,
+ /* aboveStatusBar= */ false,
+ /* view= */ null,
+ userHandle,
+ /* token */ null);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 9dc19b1..daea977 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -181,11 +181,8 @@
/**
* Returns true if heads up should be visible.
- *
- * TODO(b/138786270): If HeadsUpAppearanceController was injectable, we could inject it into
- * [KeyguardStatusBarViewController] and remove this method.
*/
- @Deprecated("deprecated in Flexiglass.") fun shouldHeadsUpBeVisible(): Boolean
+ @Deprecated("deprecated by SceneContainerFlag.isEnabled.") fun shouldHeadsUpBeVisible(): Boolean
/** Return the fraction of the shade that's expanded, when in lockscreen. */
@Deprecated("deprecated by SceneContainerFlag.isEnabled") val lockscreenShadeDragProgress: Float
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 0de3c10..18407cc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -38,6 +38,9 @@
/** Whether the Shade is fully expanded. */
val isShadeFullyExpanded: Flow<Boolean>
+ /** Whether the Shade is fully collapsed. */
+ val isShadeFullyCollapsed: Flow<Boolean>
+
/**
* Whether the user is expanding or collapsing either the shade or quick settings with user
* input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 883ef97..bb4baa3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -38,6 +38,7 @@
override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+ override val isShadeFullyCollapsed: Flow<Boolean> = inactiveFlowBoolean
override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 0b45c08..06a8d18 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -74,6 +74,9 @@
override val isShadeFullyExpanded: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
+ override val isShadeFullyCollapsed: Flow<Boolean> =
+ baseShadeInteractor.shadeExpansion.map { it <= 0f }.distinctUntilChanged()
+
override val isUserInteracting: StateFlow<Boolean> =
combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 98b52ed..4a6553f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -18,6 +18,10 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
@@ -29,13 +33,21 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) {
+class HeadsUpNotificationInteractor
+@Inject
+constructor(
+ private val headsUpRepository: HeadsUpRepository,
+ private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ private val shadeInteractor: ShadeInteractor,
+) {
- val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow
+ val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
/** Set of currently pinned top-level heads up rows to be displayed. */
val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> =
- repository.activeHeadsUpRows.flatMapLatest { repositories ->
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
if (repositories.isNotEmpty()) {
val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
repositories.map { repo -> repo.isPinned.map { isPinned -> repo to isPinned } }
@@ -50,7 +62,7 @@
/** Are there any pinned heads up rows to display? */
val hasPinnedRows: Flow<Boolean> =
- repository.activeHeadsUpRows.flatMapLatest { rows ->
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
if (rows.isNotEmpty()) {
combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
} else {
@@ -60,15 +72,38 @@
}
val isHeadsUpOrAnimatingAway: Flow<Boolean> =
- combine(hasPinnedRows, repository.isHeadsUpAnimatingAway) { hasPinnedRows, animatingAway ->
+ combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
+ hasPinnedRows,
+ animatingAway ->
hasPinnedRows || animatingAway
}
+ private val canShowHeadsUp: Flow<Boolean> =
+ combine(
+ faceAuthInteractor.isBypassEnabled,
+ shadeInteractor.isShadeFullyCollapsed,
+ keyguardTransitionInteractor.currentKeyguardState,
+ notificationsKeyguardInteractor.areNotificationsFullyHidden,
+ ) { isBypassEnabled, isShadeCollapsed, keyguardState, areNotificationsHidden ->
+ val isOnLockScreen = keyguardState == KeyguardState.LOCKSCREEN
+ when {
+ areNotificationsHidden -> false // don't show when notification are hidden
+ !isShadeCollapsed -> false // don't show when the shade is expanded
+ isOnLockScreen -> isBypassEnabled // on the lock screen only show for bypass
+ else -> true // show otherwise
+ }
+ }
+
+ val showHeadsUpStatusBar: Flow<Boolean> =
+ combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+ hasPinnedRows && canShowHeadsUp
+ }
+
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
- repository.setHeadsUpAnimatingAway(animatingAway)
+ headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 3a89630..b54f9c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -59,7 +58,6 @@
activeNotificationsInteractor: ActiveNotificationsInteractor,
notificationStackInteractor: NotificationStackInteractor,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
- keyguardInteractor: KeyguardInteractor,
remoteInputInteractor: RemoteInputInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
@@ -277,11 +275,12 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) {
- (isKeyguardShowing, isShadeFullyExpanded) ->
- // TODO(b/325936094) use isShadeFullyCollapsed instead
- !isKeyguardShowing && !isShadeFullyExpanded
- }
+ combine(
+ notificationStackInteractor.isShowingOnLockscreen,
+ shadeInteractor.isShadeFullyCollapsed
+ ) { (isKeyguardShowing, isShadeFullyCollapsed) ->
+ !isKeyguardShowing && isShadeFullyCollapsed
+ }
.dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 4c3c7d5..11feb97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -354,6 +354,7 @@
* since the headsUp manager might not have notified us yet of the state change.
*
* @return if the heads up status bar view should be shown
+ * @deprecated use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
*/
public boolean shouldBeVisible() {
boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2b26e3f..f767262 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -660,10 +660,14 @@
* whether heads up is visible.
*/
public void updateForHeadsUp() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ // [KeyguardStatusBarViewBinder] handles visibility changes due to heads up states.
+ return;
+ }
updateForHeadsUp(true);
}
- // TODO(b/328579846) bind the StatusBar visibility to heads up events
+ @VisibleForTesting
void updateForHeadsUp(boolean animate) {
boolean showingKeyguardHeadsUp =
isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index a858fb0..e5c86c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -548,6 +548,7 @@
private StatusBarVisibilityModel calculateInternalModel(
StatusBarVisibilityModel externalModel) {
+ // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
boolean headsUpVisible =
mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 5da01e2..3e01180 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import javax.inject.Inject
@@ -46,6 +47,7 @@
@Inject
constructor(
@Application scope: CoroutineScope,
+ headsUpNotificationInteractor: HeadsUpNotificationInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
batteryController: BatteryController,
@@ -55,8 +57,9 @@
combine(
keyguardInteractor.isDozing,
keyguardInteractor.statusBarState,
- ) { isDozing, statusBarState ->
- !isDozing && statusBarState == StatusBarState.KEYGUARD
+ headsUpNotificationInteractor.showHeadsUpStatusBar,
+ ) { isDozing, statusBarState, showHeadsUpStatusBar ->
+ !isDozing && statusBarState == StatusBarState.KEYGUARD && !showHeadsUpStatusBar
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
deleted file mode 100644
index aeed78a..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2020 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.util.settings;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Settings;
-
-/**
- * Used to interact with mainly with Settings.Global, but can also be used for Settings.System
- * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy}
- * must be used instead.
- * <p>
- * This interface can be implemented to give instance method (instead of static method) versions
- * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed
- * in tests.
- * <p>
- * You can ask for {@link GlobalSettings} to be injected as needed.
- * <p>
- * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
- * normally found on {@link ContentResolver} instances, unifying setting related actions in one
- * place.
- */
-public interface SettingsProxy {
-
- /**
- * Returns the {@link ContentResolver} this instance was constructed with.
- */
- ContentResolver getContentResolver();
-
- /**
- * Construct the content URI for a particular name/value pair,
- * useful for monitoring changes with a ContentObserver.
- * @param name to look up in the table
- * @return the corresponding content URI, or null if not present
- */
- Uri getUriFor(String name);
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
- * <p>
- * Implicitly calls {@link #getUriFor(String)} on the passed in name.
- */
- default void registerContentObserver(String name, ContentObserver settingsObserver) {
- registerContentObserver(getUriFor(name), settingsObserver);
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
- */
- default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
- registerContentObserver(uri, false, settingsObserver);
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
- * <p>
- * Implicitly calls {@link #getUriFor(String)} on the passed in name.
- */
- default void registerContentObserver(String name, boolean notifyForDescendants,
- ContentObserver settingsObserver) {
- registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
- */
- default void registerContentObserver(Uri uri, boolean notifyForDescendants,
- ContentObserver settingsObserver) {
- getContentResolver().registerContentObserver(
- uri, notifyForDescendants, settingsObserver);
- }
-
- /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
- default void unregisterContentObserver(ContentObserver settingsObserver) {
- getContentResolver().unregisterContentObserver(settingsObserver);
- }
-
- /**
- * Look up a name in the database.
- * @param name to look up in the table
- * @return the corresponding value, or null if not present
- */
- @Nullable
- String getString(String name);
-
- /**
- * Store a name/value pair into the database.
- * @param name to store
- * @param value to associate with the name
- * @return true if the value was set, false on database errors
- */
- boolean putString(String name, String value);
-
- /**
- * Store a name/value pair into the database.
- * <p>
- * The method takes an optional tag to associate with the setting
- * which can be used to clear only settings made by your package and
- * associated with this tag by passing the tag to {@link
- * #resetToDefaults(String)}. Anyone can override
- * the current tag. Also if another package changes the setting
- * then the tag will be set to the one specified in the set call
- * which can be null. Also any of the settings setters that do not
- * take a tag as an argument effectively clears the tag.
- * </p><p>
- * For example, if you set settings A and B with tags T1 and T2 and
- * another app changes setting A (potentially to the same value), it
- * can assign to it a tag T3 (note that now the package that changed
- * the setting is not yours). Now if you reset your changes for T1 and
- * T2 only setting B will be reset and A not (as it was changed by
- * another package) but since A did not change you are in the desired
- * initial state. Now if the other app changes the value of A (assuming
- * you registered an observer in the beginning) you would detect that
- * the setting was changed by another app and handle this appropriately
- * (ignore, set back to some value, etc).
- * </p><p>
- * Also the method takes an argument whether to make the value the
- * default for this setting. If the system already specified a default
- * value, then the one passed in here will <strong>not</strong>
- * be set as the default.
- * </p>
- *
- * @param name to store.
- * @param value to associate with the name.
- * @param tag to associate with the setting.
- * @param makeDefault whether to make the value the default one.
- * @return true if the value was set, false on database errors.
- *
- * @see #resetToDefaults(String)
- *
- */
- boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
- boolean makeDefault);
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as an integer. Note that internally setting values are always
- * stored as strings; this function converts the string to an integer
- * for you. The default value will be returned if the setting is
- * not defined or not an integer.
- *
- * @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- *
- * @return The setting's current value, or 'def' if it is not defined
- * or not a valid integer.
- */
- default int getInt(String name, int def) {
- String v = getString(name);
- try {
- return v != null ? Integer.parseInt(v) : def;
- } catch (NumberFormatException e) {
- return def;
- }
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as an integer. Note that internally setting values are always
- * stored as strings; this function converts the string to an integer
- * for you.
- * <p>
- * This version does not take a default value. If the setting has not
- * been set, or the string value is not a number,
- * it throws {@link Settings.SettingNotFoundException}.
- *
- * @param name The name of the setting to retrieve.
- *
- * @throws Settings.SettingNotFoundException Thrown if a setting by the given
- * name can't be found or the setting value is not an integer.
- *
- * @return The setting's current value.
- */
- default int getInt(String name)
- throws Settings.SettingNotFoundException {
- String v = getString(name);
- try {
- return Integer.parseInt(v);
- } catch (NumberFormatException e) {
- throw new Settings.SettingNotFoundException(name);
- }
- }
-
- /**
- * Convenience function for updating a single settings value as an
- * integer. This will either create a new entry in the table if the
- * given name does not exist, or modify the value of the existing row
- * with that name. Note that internally setting values are always
- * stored as strings, so this function converts the given value to a
- * string before storing it.
- *
- * @param name The name of the setting to modify.
- * @param value The new value for the setting.
- * @return true if the value was set, false on database errors
- */
- default boolean putInt(String name, int value) {
- return putString(name, Integer.toString(value));
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as a boolean. Note that internally setting values are always
- * stored as strings; this function converts the string to a boolean
- * for you. The default value will be returned if the setting is
- * not defined or not a boolean.
- *
- * @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- *
- * @return The setting's current value, or 'def' if it is not defined
- * or not a valid boolean.
- */
- default boolean getBool(String name, boolean def) {
- return getInt(name, def ? 1 : 0) != 0;
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as a boolean. Note that internally setting values are always
- * stored as strings; this function converts the string to a boolean
- * for you.
- * <p>
- * This version does not take a default value. If the setting has not
- * been set, or the string value is not a number,
- * it throws {@link Settings.SettingNotFoundException}.
- *
- * @param name The name of the setting to retrieve.
- *
- * @throws Settings.SettingNotFoundException Thrown if a setting by the given
- * name can't be found or the setting value is not a boolean.
- *
- * @return The setting's current value.
- */
- default boolean getBool(String name)
- throws Settings.SettingNotFoundException {
- return getInt(name) != 0;
- }
-
- /**
- * Convenience function for updating a single settings value as a
- * boolean. This will either create a new entry in the table if the
- * given name does not exist, or modify the value of the existing row
- * with that name. Note that internally setting values are always
- * stored as strings, so this function converts the given value to a
- * string before storing it.
- *
- * @param name The name of the setting to modify.
- * @param value The new value for the setting.
- * @return true if the value was set, false on database errors
- */
- default boolean putBool(String name, boolean value) {
- return putInt(name, value ? 1 : 0);
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as a {@code long}. Note that internally setting values are always
- * stored as strings; this function converts the string to a {@code long}
- * for you. The default value will be returned if the setting is
- * not defined or not a {@code long}.
- *
- * @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- *
- * @return The setting's current value, or 'def' if it is not defined
- * or not a valid {@code long}.
- */
- default long getLong(String name, long def) {
- String valString = getString(name);
- return parseLongOrUseDefault(valString, def);
- }
-
- /** Convert a string to a long, or uses a default if the string is malformed or null */
- static long parseLongOrUseDefault(String valString, long def) {
- long value;
- try {
- value = valString != null ? Long.parseLong(valString) : def;
- } catch (NumberFormatException e) {
- value = def;
- }
- return value;
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as a {@code long}. Note that internally setting values are always
- * stored as strings; this function converts the string to a {@code long}
- * for you.
- * <p>
- * This version does not take a default value. If the setting has not
- * been set, or the string value is not a number,
- * it throws {@link Settings.SettingNotFoundException}.
- *
- * @param name The name of the setting to retrieve.
- *
- * @return The setting's current value.
- * @throws Settings.SettingNotFoundException Thrown if a setting by the given
- * name can't be found or the setting value is not an integer.
- */
- default long getLong(String name)
- throws Settings.SettingNotFoundException {
- String valString = getString(name);
- return parseLongOrThrow(name, valString);
- }
-
- /** Convert a string to a long, or throws an exception if the string is malformed or null */
- static long parseLongOrThrow(String name, String valString)
- throws Settings.SettingNotFoundException {
- try {
- return Long.parseLong(valString);
- } catch (NumberFormatException e) {
- throw new Settings.SettingNotFoundException(name);
- }
- }
-
- /**
- * Convenience function for updating a secure settings value as a long
- * integer. This will either create a new entry in the table if the
- * given name does not exist, or modify the value of the existing row
- * with that name. Note that internally setting values are always
- * stored as strings, so this function converts the given value to a
- * string before storing it.
- *
- * @param name The name of the setting to modify.
- * @param value The new value for the setting.
- * @return true if the value was set, false on database errors
- */
- default boolean putLong(String name, long value) {
- return putString(name, Long.toString(value));
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as a floating point number. Note that internally setting values are
- * always stored as strings; this function converts the string to an
- * float for you. The default value will be returned if the setting
- * is not defined or not a valid float.
- *
- * @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- *
- * @return The setting's current value, or 'def' if it is not defined
- * or not a valid float.
- */
- default float getFloat(String name, float def) {
- String v = getString(name);
- return parseFloat(v, def);
- }
-
- /** Convert a string to a float, or uses a default if the string is malformed or null */
- static float parseFloat(String v, float def) {
- try {
- return v != null ? Float.parseFloat(v) : def;
- } catch (NumberFormatException e) {
- return def;
- }
- }
-
- /**
- * Convenience function for retrieving a single secure settings value
- * as a float. Note that internally setting values are always
- * stored as strings; this function converts the string to a float
- * for you.
- * <p>
- * This version does not take a default value. If the setting has not
- * been set, or the string value is not a number,
- * it throws {@link Settings.SettingNotFoundException}.
- *
- * @param name The name of the setting to retrieve.
- *
- * @throws Settings.SettingNotFoundException Thrown if a setting by the given
- * name can't be found or the setting value is not a float.
- *
- * @return The setting's current value.
- */
- default float getFloat(String name)
- throws Settings.SettingNotFoundException {
- String v = getString(name);
- return parseFloatOrThrow(name, v);
- }
-
- /** Convert a string to a float, or throws an exception if the string is malformed or null */
- static float parseFloatOrThrow(String name, String v)
- throws Settings.SettingNotFoundException {
- if (v == null) {
- throw new Settings.SettingNotFoundException(name);
- }
- try {
- return Float.parseFloat(v);
- } catch (NumberFormatException e) {
- throw new Settings.SettingNotFoundException(name);
- }
- }
-
- /**
- * Convenience function for updating a single settings value as a
- * floating point number. This will either create a new entry in the
- * table if the given name does not exist, or modify the value of the
- * existing row with that name. Note that internally setting values
- * are always stored as strings, so this function converts the given
- * value to a string before storing it.
- *
- * @param name The name of the setting to modify.
- * @param value The new value for the setting.
- * @return true if the value was set, false on database errors
- */
- default boolean putFloat(String name, float value) {
- return putString(name, Float.toString(value));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
new file mode 100644
index 0000000..ec89610
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2020 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.util.settings
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.provider.Settings.SettingNotFoundException
+
+/**
+ * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and
+ * Settings.Secure. To use the per-user System and Secure settings, [UserSettingsProxy] must be used
+ * instead.
+ *
+ * This interface can be implemented to give instance method (instead of static method) versions of
+ * Settings.Global. It can be injected into class constructors and then faked or mocked as needed in
+ * tests.
+ *
+ * You can ask for [GlobalSettings] to be injected as needed.
+ *
+ * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
+ * instances, unifying setting related actions in one place.
+ */
+interface SettingsProxy {
+ /** Returns the [ContentResolver] this instance was constructed with. */
+ fun getContentResolver(): ContentResolver
+
+ /**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ *
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
+ */
+ fun getUriFor(name: String): Uri
+
+ /**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * Implicitly calls [getUriFor] on the passed in name.
+ */
+ fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
+ registerContentObserver(getUriFor(name), settingsObserver)
+ }
+
+ /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+ registerContentObserver(uri, false, settingsObserver)
+
+ /**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * Implicitly calls [getUriFor] on the passed in name.
+ */
+ fun registerContentObserver(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver)
+
+ /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ fun registerContentObserver(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver)
+
+ /** See [ContentResolver.unregisterContentObserver]. */
+ fun unregisterContentObserver(settingsObserver: ContentObserver) =
+ getContentResolver().unregisterContentObserver(settingsObserver)
+
+ /**
+ * Look up a name in the database.
+ *
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
+ */
+ fun getString(name: String): String
+
+ /**
+ * Store a name/value pair into the database.
+ *
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ */
+ fun putString(name: String, value: String): Boolean
+
+ /**
+ * Store a name/value pair into the database.
+ *
+ * The method takes an optional tag to associate with the setting which can be used to clear
+ * only settings made by your package and associated with this tag by passing the tag to
+ * [ ][.resetToDefaults]. Anyone can override the current tag. Also if another package changes
+ * the setting then the tag will be set to the one specified in the set call which can be null.
+ * Also any of the settings setters that do not take a tag as an argument effectively clears the
+ * tag.
+ *
+ * For example, if you set settings A and B with tags T1 and T2 and another app changes setting
+ * A (potentially to the same value), it can assign to it a tag T3 (note that now the package
+ * that changed the setting is not yours). Now if you reset your changes for T1 and T2 only
+ * setting B will be reset and A not (as it was changed by another package) but since A did not
+ * change you are in the desired initial state. Now if the other app changes the value of A
+ * (assuming you registered an observer in the beginning) you would detect that the setting was
+ * changed by another app and handle this appropriately (ignore, set back to some value, etc).
+ *
+ * Also the method takes an argument whether to make the value the default for this setting. If
+ * the system already specified a default value, then the one passed in here will **not** be set
+ * as the default.
+ *
+ * @param name to store.
+ * @param value to associate with the name.
+ * @param tag to associate with the setting.
+ * @param makeDefault whether to make the value the default one.
+ * @return true if the value was set, false on database errors.
+ * @see .resetToDefaults
+ */
+ fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
+
+ /**
+ * Convenience function for retrieving a single secure settings value as an integer. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * an integer for you. The default value will be returned if the setting is not defined or not
+ * an integer.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ * @return The setting's current value, or 'def' if it is not defined or not a valid integer.
+ */
+ fun getInt(name: String, def: Int): Int {
+ val v = getString(name)
+ return try {
+ v.toInt()
+ } catch (e: NumberFormatException) {
+ def
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as an integer. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * an integer for you.
+ *
+ * This version does not take a default value. If the setting has not been set, or the string
+ * value is not a number, it throws [Settings.SettingNotFoundException].
+ *
+ * @param name The name of the setting to retrieve.
+ * @return The setting's current value.
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+ * found or the setting value is not an integer.
+ */
+ @Throws(SettingNotFoundException::class)
+ fun getInt(name: String): Int {
+ val v = getString(name)
+ return try {
+ v.toInt()
+ } catch (e: NumberFormatException) {
+ throw SettingNotFoundException(name)
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as an integer. This will either
+ * create a new entry in the table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values are always stored as
+ * strings, so this function converts the given value to a string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ fun putInt(name: String, value: Int): Boolean {
+ return putString(name, value.toString())
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a boolean. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * a boolean for you. The default value will be returned if the setting is not defined or not a
+ * boolean.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ * @return The setting's current value, or 'def' if it is not defined or not a valid boolean.
+ */
+ fun getBool(name: String, def: Boolean): Boolean {
+ return getInt(name, if (def) 1 else 0) != 0
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a boolean. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * a boolean for you.
+ *
+ * This version does not take a default value. If the setting has not been set, or the string
+ * value is not a number, it throws [Settings.SettingNotFoundException].
+ *
+ * @param name The name of the setting to retrieve.
+ * @return The setting's current value.
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+ * found or the setting value is not a boolean.
+ */
+ @Throws(SettingNotFoundException::class)
+ fun getBool(name: String): Boolean {
+ return getInt(name) != 0
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a boolean. This will either
+ * create a new entry in the table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values are always stored as
+ * strings, so this function converts the given value to a string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ fun putBool(name: String, value: Boolean): Boolean {
+ return putInt(name, if (value) 1 else 0)
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a `long`. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * a `long` for you. The default value will be returned if the setting is not defined or not a
+ * `long`.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ * @return The setting's current value, or 'def' if it is not defined or not a valid `long`.
+ */
+ fun getLong(name: String, def: Long): Long {
+ val valString = getString(name)
+ return parseLongOrUseDefault(valString, def)
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a `long`. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * a `long` for you.
+ *
+ * This version does not take a default value. If the setting has not been set, or the string
+ * value is not a number, it throws [Settings.SettingNotFoundException].
+ *
+ * @param name The name of the setting to retrieve.
+ * @return The setting's current value.
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+ * found or the setting value is not an integer.
+ */
+ @Throws(SettingNotFoundException::class)
+ fun getLong(name: String): Long {
+ val valString = getString(name)
+ return parseLongOrThrow(name, valString)
+ }
+
+ /**
+ * Convenience function for updating a secure settings value as a long integer. This will either
+ * create a new entry in the table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values are always stored as
+ * strings, so this function converts the given value to a string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ fun putLong(name: String, value: Long): Boolean {
+ return putString(name, value.toString())
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a floating point
+ * number. Note that internally setting values are always stored as strings; this function
+ * converts the string to an float for you. The default value will be returned if the setting is
+ * not defined or not a valid float.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ * @return The setting's current value, or 'def' if it is not defined or not a valid float.
+ */
+ fun getFloat(name: String, def: Float): Float {
+ val v = getString(name)
+ return parseFloat(v, def)
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a float. Note that
+ * internally setting values are always stored as strings; this function converts the string to
+ * a float for you.
+ *
+ * This version does not take a default value. If the setting has not been set, or the string
+ * value is not a number, it throws [Settings.SettingNotFoundException].
+ *
+ * @param name The name of the setting to retrieve.
+ * @return The setting's current value.
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+ * found or the setting value is not a float.
+ */
+ @Throws(SettingNotFoundException::class)
+ fun getFloat(name: String): Float {
+ val v = getString(name)
+ return parseFloatOrThrow(name, v)
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a floating point number. This
+ * will either create a new entry in the table if the given name does not exist, or modify the
+ * value of the existing row with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ fun putFloat(name: String, value: Float): Boolean {
+ return putString(name, value.toString())
+ }
+
+ companion object {
+ /** Convert a string to a long, or uses a default if the string is malformed or null */
+ @JvmStatic
+ fun parseLongOrUseDefault(valString: String, def: Long): Long {
+ val value: Long
+ value =
+ try {
+ valString.toLong()
+ } catch (e: NumberFormatException) {
+ def
+ }
+ return value
+ }
+
+ /** Convert a string to a long, or throws an exception if the string is malformed or null */
+ @JvmStatic
+ @Throws(SettingNotFoundException::class)
+ fun parseLongOrThrow(name: String, valString: String?): Long {
+ if (valString == null) {
+ throw SettingNotFoundException(name)
+ }
+ return try {
+ valString.toLong()
+ } catch (e: NumberFormatException) {
+ throw SettingNotFoundException(name)
+ }
+ }
+
+ /** Convert a string to a float, or uses a default if the string is malformed or null */
+ @JvmStatic
+ fun parseFloat(v: String?, def: Float): Float {
+ return try {
+ v?.toFloat() ?: def
+ } catch (e: NumberFormatException) {
+ def
+ }
+ }
+
+ /**
+ * Convert a string to a float, or throws an exception if the string is malformed or null
+ */
+ @JvmStatic
+ @Throws(SettingNotFoundException::class)
+ fun parseFloatOrThrow(name: String, v: String?): Float {
+ if (v == null) {
+ throw SettingNotFoundException(name)
+ }
+ return try {
+ v.toFloat()
+ } catch (e: NumberFormatException) {
+ throw SettingNotFoundException(name)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
deleted file mode 100644
index 10cf082..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.settings;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.app.tracing.TraceUtils;
-import com.android.systemui.settings.UserTracker;
-
-import kotlin.Unit;
-
-/**
- * Used to interact with per-user Settings.Secure and Settings.System settings (but not
- * Settings.Global, since those do not vary per-user)
- * <p>
- * This interface can be implemented to give instance method (instead of static method) versions
- * of Settings.Secure and Settings.System. It can be injected into class constructors and then
- * faked or mocked as needed in tests.
- * <p>
- * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed.
- * <p>
- * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
- * normally found on {@link ContentResolver} instances, unifying setting related actions in one
- * place.
- */
-public interface UserSettingsProxy extends SettingsProxy {
-
- /**
- * Returns that {@link UserTracker} this instance was constructed with.
- */
- UserTracker getUserTracker();
-
- /**
- * Returns the user id for the associated {@link ContentResolver}.
- */
- default int getUserId() {
- return getContentResolver().getUserId();
- }
-
- /**
- * Returns the actual current user handle when querying with the current user. Otherwise,
- * returns the passed in user id.
- */
- default int getRealUserHandle(int userHandle) {
- if (userHandle != UserHandle.USER_CURRENT) {
- return userHandle;
- }
- return getUserTracker().getUserId();
- }
-
- @Override
- default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
- registerContentObserverForUser(uri, settingsObserver, getUserId());
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
- */
- @Override
- default void registerContentObserver(Uri uri, boolean notifyForDescendants,
- ContentObserver settingsObserver) {
- registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
- *
- * Implicitly calls {@link #getUriFor(String)} on the passed in name.
- */
- default void registerContentObserverForUser(
- String name, ContentObserver settingsObserver, int userHandle) {
- registerContentObserverForUser(
- getUriFor(name), settingsObserver, userHandle);
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
- */
- default void registerContentObserverForUser(
- Uri uri, ContentObserver settingsObserver, int userHandle) {
- registerContentObserverForUser(
- uri, false, settingsObserver, userHandle);
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
- *
- * Implicitly calls {@link #getUriFor(String)} on the passed in name.
- */
- default void registerContentObserverForUser(
- String name, boolean notifyForDescendants, ContentObserver settingsObserver,
- int userHandle) {
- registerContentObserverForUser(
- getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
- }
-
- /**
- * Convenience wrapper around
- * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
- */
- default void registerContentObserverForUser(
- Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
- int userHandle) {
- TraceUtils.trace(
- () -> {
- // The limit for trace tags length is 127 chars, which leaves us 90 for Uri.
- return "USP#registerObserver#[" + uri.toString() + "]";
- }, () -> {
- getContentResolver().registerContentObserver(
- uri, notifyForDescendants, settingsObserver,
- getRealUserHandle(userHandle));
- return Unit.INSTANCE;
- });
- }
-
- /**
- * Look up a name in the database.
- * @param name to look up in the table
- * @return the corresponding value, or null if not present
- */
- @Override
- default String getString(String name) {
- return getStringForUser(name, getUserId());
- }
-
- /**See {@link #getString(String)}. */
- String getStringForUser(String name, int userHandle);
-
- /**
- * Store a name/value pair into the database. Values written by this method will be
- * overridden if a restore happens in the future.
- *
- * @param name to store
- * @param value to associate with the name
- * @return true if the value was set, false on database errors
- */
- boolean putString(String name, String value, boolean overrideableByRestore);
-
- @Override
- default boolean putString(String name, String value) {
- return putStringForUser(name, value, getUserId());
- }
-
- /** See {@link #putString(String, String)}. */
- boolean putStringForUser(String name, String value, int userHandle);
-
- /** See {@link #putString(String, String)}. */
- boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
- boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
-
- @Override
- default int getInt(String name, int def) {
- return getIntForUser(name, def, getUserId());
- }
-
- /** See {@link #getInt(String, int)}. */
- default int getIntForUser(String name, int def, int userHandle) {
- String v = getStringForUser(name, userHandle);
- try {
- return v != null ? Integer.parseInt(v) : def;
- } catch (NumberFormatException e) {
- return def;
- }
- }
-
- @Override
- default int getInt(String name) throws Settings.SettingNotFoundException {
- return getIntForUser(name, getUserId());
- }
-
- /** See {@link #getInt(String)}. */
- default int getIntForUser(String name, int userHandle)
- throws Settings.SettingNotFoundException {
- String v = getStringForUser(name, userHandle);
- try {
- return Integer.parseInt(v);
- } catch (NumberFormatException e) {
- throw new Settings.SettingNotFoundException(name);
- }
- }
-
- @Override
- default boolean putInt(String name, int value) {
- return putIntForUser(name, value, getUserId());
- }
-
- /** See {@link #putInt(String, int)}. */
- default boolean putIntForUser(String name, int value, int userHandle) {
- return putStringForUser(name, Integer.toString(value), userHandle);
- }
-
- @Override
- default boolean getBool(String name, boolean def) {
- return getBoolForUser(name, def, getUserId());
- }
-
- /** See {@link #getBool(String, boolean)}. */
- default boolean getBoolForUser(String name, boolean def, int userHandle) {
- return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
- }
-
- @Override
- default boolean getBool(String name) throws Settings.SettingNotFoundException {
- return getBoolForUser(name, getUserId());
- }
-
- /** See {@link #getBool(String)}. */
- default boolean getBoolForUser(String name, int userHandle)
- throws Settings.SettingNotFoundException {
- return getIntForUser(name, userHandle) != 0;
- }
-
- @Override
- default boolean putBool(String name, boolean value) {
- return putBoolForUser(name, value, getUserId());
- }
-
- /** See {@link #putBool(String, boolean)}. */
- default boolean putBoolForUser(String name, boolean value, int userHandle) {
- return putIntForUser(name, value ? 1 : 0, userHandle);
- }
-
- /** See {@link #getLong(String, long)}. */
- default long getLongForUser(String name, long def, int userHandle) {
- String valString = getStringForUser(name, userHandle);
- return SettingsProxy.parseLongOrUseDefault(valString, def);
- }
-
- /** See {@link #getLong(String)}. */
- default long getLongForUser(String name, int userHandle)
- throws Settings.SettingNotFoundException {
- String valString = getStringForUser(name, userHandle);
- return SettingsProxy.parseLongOrThrow(name, valString);
- }
-
- /** See {@link #putLong(String, long)}. */
- default boolean putLongForUser(String name, long value, int userHandle) {
- return putStringForUser(name, Long.toString(value), userHandle);
- }
-
- /** See {@link #getFloat(String)}. */
- default float getFloatForUser(String name, float def, int userHandle) {
- String v = getStringForUser(name, userHandle);
- return SettingsProxy.parseFloat(v, def);
- }
-
- /** See {@link #getFloat(String, float)}. */
- default float getFloatForUser(String name, int userHandle)
- throws Settings.SettingNotFoundException {
- String v = getStringForUser(name, userHandle);
- return SettingsProxy.parseFloatOrThrow(name, v);
- }
-
- /** See {@link #putFloat(String, float)} */
- default boolean putFloatForUser(String name, float value, int userHandle) {
- return putStringForUser(name, Float.toString(value), userHandle);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
new file mode 100644
index 0000000..2285270
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -0,0 +1,269 @@
+/*
+ * 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.systemui.util.settings
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.UserHandle
+import android.provider.Settings.SettingNotFoundException
+import com.android.app.tracing.TraceUtils.trace
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault
+
+/**
+ * Used to interact with per-user Settings.Secure and Settings.System settings (but not
+ * Settings.Global, since those do not vary per-user)
+ *
+ * This interface can be implemented to give instance method (instead of static method) versions of
+ * Settings.Secure and Settings.System. It can be injected into class constructors and then faked or
+ * mocked as needed in tests.
+ *
+ * You can ask for [SecureSettings] or [SystemSettings] to be injected as needed.
+ *
+ * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
+ * instances, unifying setting related actions in one place.
+ */
+interface UserSettingsProxy : SettingsProxy {
+
+ /** Returns that [UserTracker] this instance was constructed with. */
+ val userTracker: UserTracker
+
+ /** Returns the user id for the associated [ContentResolver]. */
+ var userId: Int
+ get() = getContentResolver().userId
+ set(_) {
+ throw UnsupportedOperationException(
+ "userId cannot be set in interface, use setter from an implementation instead."
+ )
+ }
+
+ /**
+ * Returns the actual current user handle when querying with the current user. Otherwise,
+ * returns the passed in user id.
+ */
+ fun getRealUserHandle(userHandle: Int): Int {
+ return if (userHandle != UserHandle.USER_CURRENT) {
+ userHandle
+ } else userTracker.userId
+ }
+
+ override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
+ registerContentObserverForUser(uri, settingsObserver, userId)
+ }
+
+ /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ override fun registerContentObserver(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ) {
+ registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId)
+ }
+
+ /**
+ * Convenience wrapper around [ContentResolver.registerContentObserver]
+ *
+ * Implicitly calls [getUriFor] on the passed in name.
+ */
+ fun registerContentObserverForUser(
+ name: String,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) {
+ registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle)
+ }
+
+ /** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ fun registerContentObserverForUser(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) {
+ registerContentObserverForUser(uri, false, settingsObserver, userHandle)
+ }
+
+ /**
+ * Convenience wrapper around [ContentResolver.registerContentObserver]
+ *
+ * Implicitly calls [getUriFor] on the passed in name.
+ */
+ fun registerContentObserverForUser(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) {
+ registerContentObserverForUser(
+ getUriFor(name),
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ /** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ fun registerContentObserverForUser(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) {
+ trace({ "USP#registerObserver#[$uri]" }) {
+ getContentResolver()
+ .registerContentObserver(
+ uri,
+ notifyForDescendants,
+ settingsObserver,
+ getRealUserHandle(userHandle)
+ )
+ Unit
+ }
+ }
+
+ /**
+ * Look up a name in the database.
+ *
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
+ */
+ override fun getString(name: String): String {
+ return getStringForUser(name, userId)
+ }
+
+ /** See [getString]. */
+ fun getStringForUser(name: String, userHandle: Int): String
+
+ /**
+ * Store a name/value pair into the database. Values written by this method will be overridden
+ * if a restore happens in the future.
+ *
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ */
+ fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean
+ override fun putString(name: String, value: String): Boolean {
+ return putStringForUser(name, value, userId)
+ }
+
+ /** Similar implementation to [putString] for the specified [userHandle]. */
+ fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
+
+ /** Similar implementation to [putString] for the specified [userHandle]. */
+ fun putStringForUser(
+ name: String,
+ value: String,
+ tag: String?,
+ makeDefault: Boolean,
+ @UserIdInt userHandle: Int,
+ overrideableByRestore: Boolean
+ ): Boolean
+
+ override fun getInt(name: String, def: Int): Int {
+ return getIntForUser(name, def, userId)
+ }
+
+ /** Similar implementation to [getInt] for the specified [userHandle]. */
+ fun getIntForUser(name: String, def: Int, userHandle: Int): Int {
+ val v = getStringForUser(name, userHandle)
+ return try {
+ v.toInt()
+ } catch (e: NumberFormatException) {
+ def
+ }
+ }
+
+ @Throws(SettingNotFoundException::class)
+ override fun getInt(name: String) = getIntForUser(name, userId)
+
+ /** Similar implementation to [getInt] for the specified [userHandle]. */
+ @Throws(SettingNotFoundException::class)
+ fun getIntForUser(name: String, userHandle: Int): Int {
+ val v = getStringForUser(name, userHandle)
+ return try {
+ v.toInt()
+ } catch (e: NumberFormatException) {
+ throw SettingNotFoundException(name)
+ }
+ }
+
+ override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId)
+
+ /** Similar implementation to [getInt] for the specified [userHandle]. */
+ fun putIntForUser(name: String, value: Int, userHandle: Int) =
+ putStringForUser(name, value.toString(), userHandle)
+
+ override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId)
+
+ /** Similar implementation to [getBool] for the specified [userHandle]. */
+ fun getBoolForUser(name: String, def: Boolean, userHandle: Int) =
+ getIntForUser(name, if (def) 1 else 0, userHandle) != 0
+
+ @Throws(SettingNotFoundException::class)
+ override fun getBool(name: String) = getBoolForUser(name, userId)
+
+ /** Similar implementation to [getBool] for the specified [userHandle]. */
+ @Throws(SettingNotFoundException::class)
+ fun getBoolForUser(name: String, userHandle: Int): Boolean {
+ return getIntForUser(name, userHandle) != 0
+ }
+
+ override fun putBool(name: String, value: Boolean): Boolean {
+ return putBoolForUser(name, value, userId)
+ }
+
+ /** Similar implementation to [putBool] for the specified [userHandle]. */
+ fun putBoolForUser(name: String, value: Boolean, userHandle: Int) =
+ putIntForUser(name, if (value) 1 else 0, userHandle)
+
+ /** Similar implementation to [getLong] for the specified [userHandle]. */
+ fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
+ val valString = getStringForUser(name, userHandle)
+ return parseLongOrUseDefault(valString, def)
+ }
+
+ /** Similar implementation to [getLong] for the specified [userHandle]. */
+ @Throws(SettingNotFoundException::class)
+ fun getLongForUser(name: String, userHandle: Int): Long {
+ val valString = getStringForUser(name, userHandle)
+ return parseLongOrThrow(name, valString)
+ }
+
+ /** Similar implementation to [putLong] for the specified [userHandle]. */
+ fun putLongForUser(name: String, value: Long, userHandle: Int) =
+ putStringForUser(name, value.toString(), userHandle)
+
+ /** Similar implementation to [getFloat] for the specified [userHandle]. */
+ fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
+ val v = getStringForUser(name, userHandle)
+ return parseFloat(v, def)
+ }
+
+ /** Similar implementation to [getFloat] for the specified [userHandle]. */
+ @Throws(SettingNotFoundException::class)
+ fun getFloatForUser(name: String, userHandle: Int): Float {
+ val v = getStringForUser(name, userHandle)
+ return parseFloatOrThrow(name, v)
+ }
+
+ /** Similar implementation to [putFloat] for the specified [userHandle]. */
+ fun putFloatForUser(name: String, value: Float, userHandle: Int) =
+ putStringForUser(name, value.toString(), userHandle)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
index 4812765..a714f80 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -86,7 +86,7 @@
send(MediaControllerChangeModel.ExtrasChanged(extras))
}
- override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+ override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
send(MediaControllerChangeModel.AudioInfoChanged(info))
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index 599bd73..6e1ebc8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -57,7 +57,7 @@
}
/** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */
- fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> {
+ fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo> {
return stateChanges(session) {
emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo))
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
index ef5a44a..8b5116a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
@@ -40,6 +40,6 @@
data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel
- data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) :
+ data class AudioInfoChanged(val info: MediaController.PlaybackInfo) :
MediaControllerChangeModel
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
index 0df4fbf..9ba56d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -36,12 +36,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
private const val USER_ID = 8
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 7856f9b..a89139b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -196,7 +196,7 @@
verify(globalSettings)
.registerContentObserver(
eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
- settingsObserverCaptor.capture()
+ capture(settingsObserverCaptor)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9bb21f0..9616f610 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -128,6 +128,7 @@
mContext,
TEST_PACKAGE,
mContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 2e6388a..16b00c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -129,6 +129,7 @@
mContext,
TEST_PACKAGE,
mContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 4eb0038..45ae506 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -199,6 +199,7 @@
mSpyContext,
mPackageName,
mContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -292,6 +293,7 @@
mSpyContext,
null,
mContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -333,6 +335,7 @@
mSpyContext,
null,
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -588,6 +591,7 @@
mSpyContext,
"",
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -621,6 +625,7 @@
mSpyContext,
"",
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -667,6 +672,7 @@
mSpyContext,
null,
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -693,6 +699,7 @@
mSpyContext,
null,
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -972,6 +979,7 @@
mSpyContext,
null,
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
@@ -1174,6 +1182,7 @@
mSpyContext,
null,
mSpyContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 5dbfe47..1e8fbea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -64,7 +64,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, times(1))
- .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any());
+ .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -76,7 +76,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -87,7 +87,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -101,7 +101,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -115,7 +115,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, times(1))
.createAndShow(eq(getContext().getPackageName()), eq(true), any());
}
@@ -129,7 +129,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -142,7 +142,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -155,7 +155,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -166,7 +166,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, never())
- .createAndShow(any(), anyBoolean(), any(), any());
+ .createAndShow(any(), anyBoolean(), any(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index cdef964..92d0a72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -142,6 +142,7 @@
mContext,
TEST_PACKAGE,
mContext.getUser(),
+ /* token */ null,
mMediaSessionManager,
mLocalBluetoothManager,
mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 4d32cc4..043dba1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -24,6 +24,7 @@
import static com.google.common.truth.Truth.assertThat;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import static org.mockito.ArgumentMatchers.any;
@@ -204,6 +205,7 @@
import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.channels.BufferOverflow;
import kotlinx.coroutines.test.TestScope;
import org.junit.After;
@@ -351,7 +353,6 @@
@Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
@Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
-
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -366,8 +367,11 @@
protected PowerInteractor mPowerInteractor;
protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
new FakeHeadsUpNotificationRepository();
- protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor =
- new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository);
+ protected NotificationsKeyguardViewStateRepository mNotificationsKeyguardViewStateRepository =
+ new NotificationsKeyguardViewStateRepository();
+ protected NotificationsKeyguardInteractor mNotificationsKeyguardInteractor =
+ new NotificationsKeyguardInteractor(mNotificationsKeyguardViewStateRepository);
+ protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
protected SysuiStatusBarStateController mStatusBarStateController;
@@ -417,6 +421,9 @@
mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
MutableStateFlow(false));
+ when(mKeyguardTransitionInteractor.getCurrentKeyguardState()).thenReturn(
+ MutableSharedFlow(0, 0, BufferOverflow.SUSPEND));
+ when(mDeviceEntryFaceAuthInteractor.isBypassEnabled()).thenReturn(MutableStateFlow(false));
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
@@ -670,6 +677,11 @@
when(mView.requireViewById(R.id.keyguard_long_press))
.thenReturn(mock(LongPressHandlingView.class));
+ mHeadsUpNotificationInteractor =
+ new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository,
+ mDeviceEntryFaceAuthInteractor, mKeyguardTransitionInteractor,
+ mNotificationsKeyguardInteractor, mShadeInteractor);
+
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
mMainHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 6631d29..e1ee358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -251,7 +251,7 @@
// WHEN a pinned heads up is present
mFakeHeadsUpNotificationRepository.setNotifications(
- fakeHeadsUpRowRepository("key", isPinned = true)
+ FakeHeadsUpRowRepository("key", isPinned = true)
)
}
advanceUntilIdle()
@@ -274,9 +274,4 @@
// THEN the panel should be visible
assertThat(mNotificationPanelViewController.isExpanded).isTrue()
}
-
- private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
- FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
- this.isPinned.value = isPinned
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 71f09a5..f3d6407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -181,6 +181,7 @@
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
+ mKosmos.getHeadsUpNotificationInteractor(),
mKeyguardInteractor,
new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
mBatteryController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index ab10bc4..d88289d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -21,12 +21,18 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
@@ -50,7 +56,11 @@
class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+ private val headsUpRepository by lazy { kosmos.headsUpNotificationRepository }
+ private val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
private val keyguardStatusBarInteractor by lazy { kosmos.keyguardStatusBarInteractor }
private val batteryController = kosmos.batteryController
@@ -74,6 +84,7 @@
underTest =
KeyguardStatusBarViewModel(
testScope.backgroundScope,
+ headsUpNotificationInteractor,
keyguardInteractor,
keyguardStatusBarInteractor,
batteryController,
@@ -112,7 +123,22 @@
}
@Test
- fun isVisible_statusBarStateKeyguard_andNotDozing_true() =
+ fun isVisible_headsUpStatusBarShown_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isVisible)
+
+ // WHEN HUN displayed on the bypass lock screen
+ headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
+ keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ faceAuthRepository.isBypassEnabled.value = true
+
+ // THEN KeyguardStatusBar is NOT visible to make space for HeadsUpStatusBar
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isVisible_statusBarStateKeyguard_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
new file mode 100644
index 0000000..ab95707
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings.SettingNotFoundException
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
+
+/** Tests for [SettingsProxy]. */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SettingsProxyTest : SysuiTestCase() {
+
+ private lateinit var mSettings: SettingsProxy
+ private lateinit var mContentObserver: ContentObserver
+
+ @Before
+ fun setUp() {
+ mSettings = FakeSettingsProxy()
+ mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
+ }
+
+ @Test
+ fun registerContentObserver_inputString_success() {
+ mSettings.registerContentObserver(TEST_SETTING, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+ }
+
+ @Test
+ fun registerContentObserver_inputString_notifyForDescendants_true() {
+ mSettings.registerContentObserver(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+ }
+
+ @Test
+ fun registerContentObserver_inputUri_success() {
+ mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+ }
+
+ @Test
+ fun registerContentObserver_inputUri_notifyForDescendants_true() {
+ mSettings.registerContentObserver(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+ }
+
+ @Test
+ fun unregisterContentObserver() {
+ mSettings.unregisterContentObserver(mContentObserver)
+ verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
+ }
+
+ @Test
+ fun getString_keyPresent_returnValidValue() {
+ mSettings.putString(TEST_SETTING, "test")
+ assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test")
+ }
+
+ @Test
+ fun getString_keyAbsent_returnEmptyValue() {
+ assertThat(mSettings.getString(TEST_SETTING)).isEmpty()
+ }
+
+ @Test
+ fun getInt_keyPresent_returnValidValue() {
+ mSettings.putInt(TEST_SETTING, 2)
+ assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2)
+ }
+
+ @Test
+ fun getInt_keyPresent_nonIntegerValue_throwException() {
+ assertThrows(SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getInt(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getInt_keyAbsent_throwException() {
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getInt(TEST_SETTING) }
+ }
+
+ @Test
+ fun getInt_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5)
+ }
+
+ @Test
+ fun getBool_keyPresent_returnValidValue() {
+ mSettings.putBool(TEST_SETTING, true)
+ assertThat(mSettings.getBool(TEST_SETTING)).isTrue()
+ }
+
+ @Test
+ fun getBool_keyPresent_nonBooleanValue_throwException() {
+ assertThrows(SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getBool(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getBool_keyAbsent_throwException() {
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getBool(TEST_SETTING) }
+ }
+
+ @Test
+ fun getBool_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false)
+ }
+
+ @Test
+ fun getLong_keyPresent_returnValidValue() {
+ mSettings.putLong(TEST_SETTING, 1L)
+ assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L)
+ }
+
+ @Test
+ fun getLong_keyPresent_nonLongValue_throwException() {
+ assertThrows(SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getLong(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getLong_keyAbsent_throwException() {
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getLong(TEST_SETTING) }
+ }
+
+ @Test
+ fun getLong_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L)
+ }
+
+ @Test
+ fun getFloat_keyPresent_returnValidValue() {
+ mSettings.putFloat(TEST_SETTING, 2.5F)
+ assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F)
+ }
+
+ @Test
+ fun getFloat_keyPresent_nonFloatValue_throwException() {
+ assertThrows(SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getFloat(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getFloat_keyAbsent_throwException() {
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getFloat(TEST_SETTING) }
+ }
+
+ @Test
+ fun getFloat_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
+ }
+
+ private class FakeSettingsProxy : SettingsProxy {
+
+ private val mContentResolver = mock(ContentResolver::class.java)
+ private val settingToValueMap: MutableMap<String, String> = mutableMapOf()
+
+ override fun getContentResolver() = mContentResolver
+
+ override fun getUriFor(name: String) =
+ Uri.parse(StringBuilder().append("content://settings/").append(name).toString())
+
+ override fun getString(name: String): String {
+ return settingToValueMap[name] ?: ""
+ }
+
+ override fun putString(name: String, value: String): Boolean {
+ settingToValueMap[name] = value
+ return true
+ }
+
+ override fun putString(
+ name: String,
+ value: String,
+ tag: String,
+ makeDefault: Boolean
+ ): Boolean {
+ settingToValueMap[name] = value
+ return true
+ }
+ }
+
+ companion object {
+ private const val TEST_SETTING = "test_setting"
+ private val TEST_SETTING_URI = Uri.parse("content://settings/test_setting")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
new file mode 100644
index 0000000..56328b9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -0,0 +1,365 @@
+/*
+ * 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.util.settings
+
+import android.content.ContentResolver
+import android.content.pm.UserInfo
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
+
+/** Tests for [UserSettingsProxy]. */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UserSettingsProxyTest : SysuiTestCase() {
+
+ private var mUserTracker = FakeUserTracker()
+ private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker)
+ private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
+
+ @Before
+ fun setUp() {
+ mUserTracker.set(
+ listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)),
+ selectedUserIndex = 0
+ )
+ }
+
+ @Test
+ fun registerContentObserverForUser_inputString_success() {
+ mSettings.registerContentObserverForUser(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
+
+ @Test
+ fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
+ mSettings.registerContentObserverForUser(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
+
+ @Test
+ fun registerContentObserverForUser_inputUri_success() {
+ mSettings.registerContentObserverForUser(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
+
+ @Test
+ fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
+ mSettings.registerContentObserverForUser(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
+
+ @Test
+ fun registerContentObserver_inputUri_success() {
+ mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
+ }
+
+ @Test
+ fun registerContentObserver_inputUri_notifyForDescendants_true() {
+ mSettings.registerContentObserver(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0))
+ }
+
+ @Test
+ fun getString_keyPresent_returnValidValue() {
+ mSettings.putString(TEST_SETTING, "test")
+ assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test")
+ }
+
+ @Test
+ fun getString_keyAbsent_returnEmptyValue() {
+ assertThat(mSettings.getString(TEST_SETTING)).isEmpty()
+ }
+
+ @Test
+ fun getStringForUser_multipleUsers_validResult() {
+ mSettings.putStringForUser(TEST_SETTING, "test", MAIN_USER_ID)
+ mSettings.putStringForUser(TEST_SETTING, "test1", SECONDARY_USER_ID)
+ assertThat(mSettings.getStringForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo("test")
+ assertThat(mSettings.getStringForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo("test1")
+ }
+
+ @Test
+ fun getInt_keyPresent_returnValidValue() {
+ mSettings.putInt(TEST_SETTING, 2)
+ assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2)
+ }
+
+ @Test
+ fun getInt_keyPresent_nonIntegerValue_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getInt(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getInt_keyAbsent_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.getInt(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getInt_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5)
+ }
+
+ @Test
+ fun getIntForUser_multipleUsers__validResult() {
+ mSettings.putIntForUser(TEST_SETTING, 1, MAIN_USER_ID)
+ mSettings.putIntForUser(TEST_SETTING, 2, SECONDARY_USER_ID)
+ assertThat(mSettings.getIntForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1)
+ assertThat(mSettings.getIntForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2)
+ }
+
+ @Test
+ fun getBool_keyPresent_returnValidValue() {
+ mSettings.putBool(TEST_SETTING, true)
+ assertThat(mSettings.getBool(TEST_SETTING)).isTrue()
+ }
+
+ @Test
+ fun getBool_keyPresent_nonBooleanValue_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getBool(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getBool_keyAbsent_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.getBool(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getBool_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false)
+ }
+
+ @Test
+ fun getBoolForUser_multipleUsers__validResult() {
+ mSettings.putBoolForUser(TEST_SETTING, true, MAIN_USER_ID)
+ mSettings.putBoolForUser(TEST_SETTING, false, SECONDARY_USER_ID)
+ assertThat(mSettings.getBoolForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(true)
+ assertThat(mSettings.getBoolForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(false)
+ }
+
+ @Test
+ fun getLong_keyPresent_returnValidValue() {
+ mSettings.putLong(TEST_SETTING, 1L)
+ assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L)
+ }
+
+ @Test
+ fun getLong_keyPresent_nonLongValue_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getLong(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getLong_keyAbsent_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.getLong(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getLong_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L)
+ }
+
+ @Test
+ fun getLongForUser_multipleUsers__validResult() {
+ mSettings.putLongForUser(TEST_SETTING, 1L, MAIN_USER_ID)
+ mSettings.putLongForUser(TEST_SETTING, 2L, SECONDARY_USER_ID)
+ assertThat(mSettings.getLongForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1L)
+ assertThat(mSettings.getLongForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2L)
+ }
+
+ @Test
+ fun getFloat_keyPresent_returnValidValue() {
+ mSettings.putFloat(TEST_SETTING, 2.5F)
+ assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F)
+ }
+
+ @Test
+ fun getFloat_keyPresent_nonFloatValue_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.putString(TEST_SETTING, "test")
+ mSettings.getFloat(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getFloat_keyAbsent_throwException() {
+ assertThrows(Settings.SettingNotFoundException::class.java) {
+ mSettings.getFloat(TEST_SETTING)
+ }
+ }
+
+ @Test
+ fun getFloat_keyAbsent_returnDefaultValue() {
+ assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
+ }
+
+ @Test
+ fun getFloatForUser_multipleUsers__validResult() {
+ mSettings.putFloatForUser(TEST_SETTING, 1F, MAIN_USER_ID)
+ mSettings.putFloatForUser(TEST_SETTING, 2F, SECONDARY_USER_ID)
+ assertThat(mSettings.getFloatForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1F)
+ assertThat(mSettings.getFloatForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2F)
+ }
+
+ /**
+ * Fake implementation of [UserSettingsProxy].
+ *
+ * This class uses a mock of [ContentResolver] to test the [ContentObserver] registration APIs.
+ */
+ private class FakeUserSettingsProxy(override val userTracker: UserTracker) : UserSettingsProxy {
+
+ private val mContentResolver = mock(ContentResolver::class.java)
+ private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> =
+ mutableMapOf()
+
+ override fun getContentResolver() = mContentResolver
+
+ override fun getUriFor(name: String) =
+ Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString())
+
+ override fun getStringForUser(name: String, userHandle: Int) =
+ userIdToSettingsValueMap[userHandle]?.get(name) ?: ""
+
+ override fun putString(
+ name: String,
+ value: String,
+ overrideableByRestore: Boolean
+ ): Boolean {
+ userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value)
+ return true
+ }
+
+ override fun putString(
+ name: String,
+ value: String,
+ tag: String,
+ makeDefault: Boolean
+ ): Boolean {
+ putStringForUser(name, value, DEFAULT_USER_ID)
+ return true
+ }
+
+ override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean {
+ userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value))
+ return true
+ }
+
+ override fun putStringForUser(
+ name: String,
+ value: String,
+ tag: String?,
+ makeDefault: Boolean,
+ userHandle: Int,
+ overrideableByRestore: Boolean
+ ): Boolean {
+ userIdToSettingsValueMap[userHandle]?.set(name, value)
+ return true
+ }
+
+ private companion object {
+ const val DEFAULT_USER_ID = 0
+ const val URI_PREFIX = "content://settings/"
+ }
+ }
+
+ private companion object {
+ const val MAIN_USER_ID = 10
+ const val SECONDARY_USER_ID = 20
+ const val TEST_SETTING = "test_setting"
+ val TEST_SETTING_URI = Uri.parse("content://settings/test_setting")
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index a81ac03..0e95320 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -55,6 +55,7 @@
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -78,6 +79,7 @@
val configurationInteractor by lazy { kosmos.configurationInteractor }
val bouncerRepository by lazy { kosmos.bouncerRepository }
val communalRepository by lazy { kosmos.fakeCommunalRepository }
+ val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
index 2e983a8..980d65f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
@@ -18,7 +18,11 @@
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any) :
+class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any = Any()) :
HeadsUpRowRepository {
+ constructor(key: String, isPinned: Boolean) : this(key = key) {
+ this.isPinned.value = isPinned
+ }
+
override val isPinned: MutableStateFlow<Boolean> = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
index d345107..c74aec1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
@@ -16,11 +16,20 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
val Kosmos.headsUpNotificationInteractor by Fixture {
- HeadsUpNotificationInteractor(headsUpNotificationRepository)
+ HeadsUpNotificationInteractor(
+ headsUpNotificationRepository,
+ deviceEntryFaceAuthInteractor,
+ keyguardTransitionInteractor,
+ notificationsKeyguardInteractor,
+ shadeInteractor,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 94f6ecd..de8b350 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.dump.dumpManager
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
@@ -42,7 +41,6 @@
activeNotificationsInteractor,
notificationStackInteractor,
headsUpNotificationInteractor,
- keyguardInteractor,
remoteInputInteractor,
seenNotificationsInteractor,
shadeInteractor,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 58855ea..4c8f416 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5590,32 +5590,30 @@
// security checking for it above.
userId = UserHandle.USER_CURRENT;
}
- try {
- if (owningUid != 0 && owningUid != SYSTEM_UID) {
- final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
- MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(owningUid));
- if (!UserHandle.isSameApp(owningUid, uid)) {
- String msg = "Permission Denial: getIntentSender() from pid="
- + Binder.getCallingPid()
- + ", uid=" + owningUid
- + ", (need uid=" + uid + ")"
- + " is not allowed to send as package " + packageName;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
- if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
- return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid,
- userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
- bOptions);
+ if (owningUid != 0 && owningUid != SYSTEM_UID) {
+ if (!getPackageManagerInternal().isSameApp(
+ packageName,
+ MATCH_DEBUG_TRIAGED_MISSING,
+ owningUid,
+ UserHandle.getUserId(owningUid))) {
+ String msg = "Permission Denial: getIntentSender() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + owningUid
+ + " is not allowed to send as package " + packageName;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
- return mPendingIntentController.getIntentSender(type, packageName, featureId,
- owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes,
- flags, bOptions);
- } catch (RemoteException e) {
- throw new SecurityException(e);
}
+
+ if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
+ return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid,
+ userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
+ bOptions);
+ }
+ return mPendingIntentController.getIntentSender(type, packageName, featureId,
+ owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes,
+ flags, bOptions);
}
@Override
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 9600317..a67af89 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -84,6 +84,7 @@
import java.util.Arrays;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.ToIntFunction;
/**
* A modern implementation of the oom adjuster.
@@ -271,11 +272,31 @@
// The last node besides the tail.
private final ProcessRecordNode[] mLastNode;
+ private final ToIntFunction<ProcessRecord> mSlotFunction;
+ // Cache of the most important slot with a node in it.
+ private int mFirstPopulatedSlot = 0;
+
ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) {
mType = type;
+ final ToIntFunction<ProcessRecord> valueFunction;
+ switch (mType) {
+ case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+ valueFunction = (proc) -> proc.mState.getCurProcState();
+ mSlotFunction = (proc) -> processStateToSlot(proc.mState.getCurProcState());
+ break;
+ case ProcessRecordNode.NODE_TYPE_ADJ:
+ valueFunction = (proc) -> proc.mState.getCurRawAdj();
+ mSlotFunction = (proc) -> adjToSlot(proc.mState.getCurRawAdj());
+ break;
+ default:
+ valueFunction = (proc) -> 0;
+ mSlotFunction = (proc) -> 0;
+ break;
+ }
+
mProcessRecordNodes = new LinkedProcessRecordList[size];
for (int i = 0; i < size; i++) {
- mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
+ mProcessRecordNodes[i] = new LinkedProcessRecordList(valueFunction);
}
mLastNode = new ProcessRecordNode[size];
resetLastNodes();
@@ -294,6 +315,11 @@
}
void resetLastNodes() {
+ if (Flags.simplifyProcessTraversal()) {
+ // Last nodes are no longer used. Just reset instead.
+ reset();
+ return;
+ }
for (int i = 0; i < mProcessRecordNodes.length; i++) {
mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail();
}
@@ -308,6 +334,36 @@
final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
while (node != tail) {
mTmpOomAdjusterArgs.mApp = node.mApp;
+ if (node.mApp == null) {
+ // TODO(b/336178916) - Temporary logging for root causing b/336178916.
+ StringBuilder sb = new StringBuilder();
+ sb.append("Iterating null process during OomAdjuster traversal!!!\n");
+ sb.append("Type:");
+ switch (mType) {
+ case ProcessRecordNode.NODE_TYPE_PROC_STATE -> sb.append(
+ "NODE_TYPE_PROC_STATE");
+ case ProcessRecordNode.NODE_TYPE_ADJ -> sb.append("NODE_TYPE_ADJ");
+ default -> sb.append("UNKNOWN");
+ }
+ sb.append(", Slot:");
+ sb.append(slot);
+ sb.append("\nLAST:");
+ ProcessRecordNode last = mLastNode[slot];
+ if (last.mApp == null) {
+ sb.append("null");
+ } else {
+ sb.append(last);
+ sb.append("\nSetProcState:");
+ sb.append(last.mApp.getSetProcState());
+ sb.append(", CurProcState:");
+ sb.append(last.mApp.mState.getCurProcState());
+ sb.append(", SetAdj:");
+ sb.append(last.mApp.getSetAdj());
+ sb.append(", CurRawAdj:");
+ sb.append(last.mApp.mState.getCurRawAdj());
+ }
+ Slog.wtfStack(TAG, sb.toString());
+ }
// Save the next before calling callback, since that may change the node.mNext.
final ProcessRecordNode next = node.mNext;
callback.accept(mTmpOomAdjusterArgs);
@@ -325,6 +381,33 @@
}
}
+ ProcessRecord poll() {
+ ProcessRecordNode node = null;
+ final int size = mProcessRecordNodes.length;
+ // Find the next node.
+ while (node == null && mFirstPopulatedSlot < size) {
+ node = mProcessRecordNodes[mFirstPopulatedSlot].poll();
+ if (node == null) {
+ // This slot is now empty, move on to the next.
+ mFirstPopulatedSlot++;
+ }
+ }
+ if (node == null) return null;
+ return node.mApp;
+ }
+
+ void offer(ProcessRecord proc) {
+ ProcessRecordNode node = proc.mLinkedNodes[mType];
+ // Find which slot to add the node to.
+ final int newSlot = mSlotFunction.applyAsInt(proc);
+ if (newSlot < mFirstPopulatedSlot) {
+ // node is being added to a more important slot.
+ mFirstPopulatedSlot = newSlot;
+ }
+ node.unlink();
+ mProcessRecordNodes[newSlot].offer(node);
+ }
+
int getNumberOfSlots() {
return mProcessRecordNodes.length;
}
@@ -423,12 +506,35 @@
// Sentinel head/tail, to make bookkeeping work easier.
final ProcessRecordNode HEAD = new ProcessRecordNode(null);
final ProcessRecordNode TAIL = new ProcessRecordNode(null);
- final @ProcessRecordNode.NodeType int mNodeType;
+ final ToIntFunction<ProcessRecord> mValueFunction;
- LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) {
+ LinkedProcessRecordList(ToIntFunction<ProcessRecord> valueFunction) {
HEAD.mNext = TAIL;
TAIL.mPrev = HEAD;
- mNodeType = nodeType;
+ mValueFunction = valueFunction;
+ }
+
+ ProcessRecordNode poll() {
+ final ProcessRecordNode next = HEAD.mNext;
+ if (next == TAIL) return null;
+ next.unlink();
+ return next;
+ }
+
+ void offer(@NonNull ProcessRecordNode node) {
+ final int newValue = mValueFunction.applyAsInt(node.mApp);
+
+ // Find the last node with less than or equal value to the new node.
+ ProcessRecordNode curNode = TAIL.mPrev;
+ while (curNode != HEAD && mValueFunction.applyAsInt(curNode.mApp) > newValue) {
+ curNode = curNode.mPrev;
+ }
+
+ // Insert the new node after the found node.
+ node.mPrev = curNode;
+ node.mNext = curNode.mNext;
+ curNode.mNext.mPrev = node;
+ curNode.mNext = node;
}
void append(@NonNull ProcessRecordNode node) {
@@ -727,34 +833,50 @@
private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) {
if (app.mState.getCurRawAdj() != prevRawAdj) {
- final int slot = adjToSlot(app.mState.getCurRawAdj());
- final int prevSlot = adjToSlot(prevRawAdj);
- if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
- mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ if (Flags.simplifyProcessTraversal()) {
+ mProcessRecordAdjNodes.offer(app);
+ } else {
+ final int slot = adjToSlot(app.mState.getCurRawAdj());
+ final int prevSlot = adjToSlot(prevRawAdj);
+ if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
+ mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ }
}
}
}
private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
- final int slot = adjToSlot(app.mState.getCurRawAdj());
- final int prevSlot = adjToSlot(prevRawAdj);
- mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ if (Flags.simplifyProcessTraversal()) {
+ mProcessRecordAdjNodes.offer(app);
+ } else {
+ final int slot = adjToSlot(app.mState.getCurRawAdj());
+ final int prevSlot = adjToSlot(prevRawAdj);
+ mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ }
}
private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
if (app.mState.getCurProcState() != prevProcState) {
- final int slot = processStateToSlot(app.mState.getCurProcState());
- final int prevSlot = processStateToSlot(prevProcState);
- if (slot != prevSlot) {
- mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ if (Flags.simplifyProcessTraversal()) {
+ mProcessRecordProcStateNodes.offer(app);
+ } else {
+ final int slot = processStateToSlot(app.mState.getCurProcState());
+ final int prevSlot = processStateToSlot(prevProcState);
+ if (slot != prevSlot) {
+ mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ }
}
}
}
private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
- final int slot = processStateToSlot(app.mState.getCurProcState());
- final int prevSlot = processStateToSlot(prevProcState);
- mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ if (Flags.simplifyProcessTraversal()) {
+ mProcessRecordProcStateNodes.offer(app);
+ } else {
+ final int slot = processStateToSlot(app.mState.getCurProcState());
+ final int prevSlot = processStateToSlot(prevProcState);
+ mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ }
}
@Override
@@ -832,8 +954,15 @@
// Compute initial values, the procState and adj priority queues will be populated here.
computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
false);
- updateProcStateSlot(app, prevProcState);
- updateAdjSlot(app, prevAdj);
+
+ if (Flags.simplifyProcessTraversal()) {
+ // Just add to the procState priority queue. The adj priority queue should be
+ // empty going into the traversal step.
+ mProcessRecordProcStateNodes.offer(app);
+ } else {
+ updateProcStateSlot(app, prevProcState);
+ updateAdjSlot(app, prevAdj);
+ }
}
// Set adj last nodes now, this way a process will only be reevaluated during the adj node
@@ -851,14 +980,32 @@
*/
@GuardedBy({"mService", "mProcLock"})
private void computeConnectionsLSP() {
- // 1st pass, scan each slot in the procstate node list.
- for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
- mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
- }
+ if (Flags.simplifyProcessTraversal()) {
+ // 1st pass, iterate all nodes in order of procState importance.
+ ProcessRecord proc = mProcessRecordProcStateNodes.poll();
+ while (proc != null) {
+ mTmpOomAdjusterArgs.mApp = proc;
+ mComputeConnectionsConsumer.accept(mTmpOomAdjusterArgs);
+ proc = mProcessRecordProcStateNodes.poll();
+ }
- // 2nd pass, scan each slot in the adj node list.
- for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
- mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
+ // 2st pass, iterate all nodes in order of procState importance.
+ proc = mProcessRecordAdjNodes.poll();
+ while (proc != null) {
+ mTmpOomAdjusterArgs.mApp = proc;
+ mComputeConnectionsConsumer.accept(mTmpOomAdjusterArgs);
+ proc = mProcessRecordAdjNodes.poll();
+ }
+ } else {
+ // 1st pass, scan each slot in the procstate node list.
+ for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
+ mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
+ }
+
+ // 2nd pass, scan each slot in the adj node list.
+ for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
+ mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
+ }
}
}
@@ -987,8 +1134,14 @@
args.mApp = reachable;
computeOomAdjIgnoringReachablesLSP(args);
- updateProcStateSlot(reachable, prevProcState);
- updateAdjSlot(reachable, prevAdj);
+ if (Flags.simplifyProcessTraversal()) {
+ // Just add to the procState priority queue. The adj priority queue should be
+ // empty going into the traversal step.
+ mProcessRecordProcStateNodes.offer(reachable);
+ } else {
+ updateProcStateSlot(reachable, prevProcState);
+ updateAdjSlot(reachable, prevAdj);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index da45a77..8d7a1c9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,6 +18,10 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -389,13 +393,20 @@
private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
@Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
- if (options == null || !options.containsKey(
- ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
+ if (options == null) {
return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
- return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
- ? BackgroundStartPrivileges.ALLOW_BAL
- : BackgroundStartPrivileges.NONE;
+ switch (options.getInt(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)) {
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED:
+ return BackgroundStartPrivileges.NONE;
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ default:
+ return BackgroundStartPrivileges.ALLOW_BAL;
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9bf5c21..032093b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -342,33 +342,29 @@
AsyncTask.THREAD_POOL_EXECUTOR,
(DeviceConfig.Properties properties) -> {
- HashMap<String, HashMap<String, String>> propsToStage =
- getStagedFlagsWithValueChange(properties);
-
- // send prop stage request to sys prop
- for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
- String actualNamespace = entry.getKey();
- HashMap<String, String> flagValuesToStage = entry.getValue();
-
- for (String flagName : flagValuesToStage.keySet()) {
- String stagedValue = flagValuesToStage.get(flagName);
- String propertyName = "next_boot." + makeAconfigFlagPropertyName(
- actualNamespace, flagName);
-
- if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
- || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
- logErr("unable to construct system property for " + actualNamespace
- + "/" + flagName);
- continue;
+ for (String flagName : properties.getKeyset()) {
+ String flagValue = properties.getString(flagName, null);
+ if (flagName == null || flagValue == null) {
+ continue;
}
- setProperty(propertyName, stagedValue);
- }
+ int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ logErr("invalid staged flag: " + flagName);
+ continue;
+ }
+
+ String actualNamespace = flagName.substring(0, idx);
+ String actualFlagName = flagName.substring(idx+1);
+ String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+ actualNamespace, actualFlagName);
+
+ setProperty(propertyName, flagValue);
}
// send prop stage request to new storage
if (enableAconfigStorageDaemon()) {
- stageFlagsInNewStorage(propsToStage);
+ stageFlagsInNewStorage(properties);
}
});
@@ -607,25 +603,33 @@
* @param propsToStage
*/
@VisibleForTesting
- static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
+ static void stageFlagsInNewStorage(DeviceConfig.Properties props) {
// write aconfigd requests proto to proto output stream
int num_requests = 0;
ProtoOutputStream requests = new ProtoOutputStream();
- for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
- String actualNamespace = entry.getKey();
- HashMap<String, String> flagValuesToStage = entry.getValue();
- for (String fullFlagName : flagValuesToStage.keySet()) {
- String stagedValue = flagValuesToStage.get(fullFlagName);
- int idx = fullFlagName.lastIndexOf(".");
- if (idx == -1) {
- logErr("invalid flag name: " + fullFlagName);
- continue;
- }
- String packageName = fullFlagName.substring(0, idx);
- String flagName = fullFlagName.substring(idx+1);
- writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false);
- ++num_requests;
+ for (String flagName : props.getKeyset()) {
+ String flagValue = props.getString(flagName, null);
+ if (flagName == null || flagValue == null) {
+ continue;
}
+
+ int idx = flagName.indexOf("*");
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ logErr("invalid local flag override: " + flagName);
+ continue;
+ }
+ String actualNamespace = flagName.substring(0, idx);
+ String fullFlagName = flagName.substring(idx+1);
+
+ idx = fullFlagName.lastIndexOf(".");
+ if (idx == -1) {
+ logErr("invalid flag name: " + fullFlagName);
+ continue;
+ }
+ String packageName = fullFlagName.substring(0, idx);
+ String realFlagName = fullFlagName.substring(idx+1);
+ writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, false);
+ ++num_requests;
}
if (num_requests == 0) {
@@ -637,7 +641,7 @@
// deserialize back using proto input stream
try {
- parseAndLogAconfigdReturn(returns);
+ parseAndLogAconfigdReturn(returns);
} catch (IOException ioe) {
logErr("failed to parse aconfigd return", ioe);
}
@@ -665,63 +669,6 @@
return propertyName;
}
- /**
- * Get the flags that need to be staged in sys prop, only these with a real value
- * change needs to be staged in sys prop. Otherwise, the flag stage is useless and
- * create performance problem at sys prop side.
- * @param properties
- * @return a hash map of namespace name to actual flags to stage
- */
- @VisibleForTesting
- static HashMap<String, HashMap<String, String>> getStagedFlagsWithValueChange(
- DeviceConfig.Properties properties) {
-
- // sort flags by actual namespace of the flag
- HashMap<String, HashMap<String, String>> stagedProps = new HashMap<>();
- for (String flagName : properties.getKeyset()) {
- int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
- if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- logErr("invalid staged flag: " + flagName);
- continue;
- }
- String actualNamespace = flagName.substring(0, idx);
- String actualFlagName = flagName.substring(idx+1);
- HashMap<String, String> flagStagedValues = stagedProps.get(actualNamespace);
- if (flagStagedValues == null) {
- flagStagedValues = new HashMap<String, String>();
- stagedProps.put(actualNamespace, flagStagedValues);
- }
- flagStagedValues.put(actualFlagName, properties.getString(flagName, null));
- }
-
- // for each namespace, find flags with real flag value change
- HashMap<String, HashMap<String, String>> propsToStage = new HashMap<>();
- for (HashMap.Entry<String, HashMap<String, String>> entry : stagedProps.entrySet()) {
- String actualNamespace = entry.getKey();
- HashMap<String, String> flagStagedValues = entry.getValue();
- Map<String, String> flagCurrentValues = Settings.Config.getStrings(
- actualNamespace, new ArrayList<String>(flagStagedValues.keySet()));
-
- HashMap<String, String> flagsToStage = new HashMap<>();
- for (String flagName : flagStagedValues.keySet()) {
- String stagedValue = flagStagedValues.get(flagName);
- String currentValue = flagCurrentValues.get(flagName);
- if (stagedValue == null) {
- continue;
- }
- if (currentValue == null || !stagedValue.equalsIgnoreCase(currentValue)) {
- flagsToStage.put(flagName, stagedValue);
- }
- }
-
- if (!flagsToStage.isEmpty()) {
- propsToStage.put(actualNamespace, flagsToStage);
- }
- }
-
- return propsToStage;
- }
-
private void setProperty(String key, String value) {
// Check if need to clear the property
if (value == null) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index b7108df..afde4f7 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -123,3 +123,14 @@
description: "Avoid OomAdjuster calculations for connections that won't change importance"
bug: "323376416"
}
+
+flag {
+ name: "simplify_process_traversal"
+ namespace: "backstage_power"
+ description: "Simplify the OomAdjuster's process traversal mechanism."
+ bug: "336178916"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 475334c..1dc1846 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1439,7 +1439,6 @@
sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
}
- @GuardedBy("mDeviceStateLock")
/*package*/ void postBluetoothActiveDevice(BtDeviceInfo info, int delay) {
sendLMsg(MSG_L_SET_BT_ACTIVE_DEVICE, SENDMSG_QUEUE, info, delay);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 15c5c10..7deef2f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7897,7 +7897,7 @@
+ previousDevice + " -> " + newDevice + ". Got: " + profile);
}
- sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+ sDeviceLogger.enqueue(new EventLogger.StringEvent("BluetoothActiveDeviceChanged for "
+ BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
+ " -> " + newDevice).printLog(TAG));
AudioDeviceBroker.BtDeviceChangedData data =
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index a649d34..07daecd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -289,6 +289,7 @@
Log.e(TAG, "Exception while getting status of " + device, e);
}
if (btCodecStatus == null) {
+ Log.e(TAG, "getCodec, null A2DP codec status for device: " + device);
mA2dpCodecConfig = null;
return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
@@ -316,6 +317,7 @@
Log.e(TAG, "Exception while getting status of " + device, e);
}
if (btLeCodecStatus == null) {
+ Log.e(TAG, "getCodec, null LE codec status for device: " + device);
mLeAudioCodecConfig = null;
return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
@@ -363,6 +365,7 @@
return new Pair<>(profile == BluetoothProfile.A2DP
? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true);
}
+
return codecAndChanged;
}
@@ -653,7 +656,7 @@
// Not a valid profile to connect
Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect "
+ BluetoothProfile.getProfileName(profile));
- break;
+ return;
}
// this part is only for A2DP, LE Audio unicast and Hearing aid
@@ -664,17 +667,65 @@
return;
}
List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile);
- BluetoothProfileConnectionInfo bpci = new BluetoothProfileConnectionInfo(profile);
- for (BluetoothDevice device : activeDevices) {
- if (device == null) {
- continue;
- }
- AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
- device, null, bpci, "mBluetoothProfileServiceListener");
- AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
- data, device, BluetoothProfile.STATE_CONNECTED);
- mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
+ if (activeDevices.isEmpty() || activeDevices.get(0) == null) {
+ return;
}
+ BluetoothDevice device = activeDevices.get(0);
+ switch (profile) {
+ case BluetoothProfile.A2DP: {
+ BluetoothProfileConnectionInfo bpci =
+ BluetoothProfileConnectionInfo.createA2dpInfo(false, -1);
+ postBluetoothActiveDevice(device, bpci);
+ } break;
+ case BluetoothProfile.HEARING_AID: {
+ BluetoothProfileConnectionInfo bpci =
+ BluetoothProfileConnectionInfo.createHearingAidInfo(false);
+ postBluetoothActiveDevice(device, bpci);
+ } break;
+ case BluetoothProfile.LE_AUDIO: {
+ int groupId = mLeAudio.getGroupId(device);
+ BluetoothLeAudioCodecStatus btLeCodecStatus = null;
+ try {
+ btLeCodecStatus = mLeAudio.getCodecStatus(groupId);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while getting status of " + device, e);
+ }
+ if (btLeCodecStatus == null) {
+ Log.i(TAG, "onBtProfileConnected null LE codec status for groupId: "
+ + groupId + ", device: " + device);
+ break;
+ }
+ List<BluetoothLeAudioCodecConfig> outputCodecConfigs =
+ btLeCodecStatus.getOutputCodecSelectableCapabilities();
+ if (!outputCodecConfigs.isEmpty()) {
+ BluetoothProfileConnectionInfo bpci =
+ BluetoothProfileConnectionInfo.createLeAudioInfo(
+ false /*suppressNoisyIntent*/, true /*isLeOutput*/);
+ postBluetoothActiveDevice(device, bpci);
+ }
+ List<BluetoothLeAudioCodecConfig> inputCodecConfigs =
+ btLeCodecStatus.getInputCodecSelectableCapabilities();
+ if (!inputCodecConfigs.isEmpty()) {
+ BluetoothProfileConnectionInfo bpci =
+ BluetoothProfileConnectionInfo.createLeAudioInfo(
+ false /*suppressNoisyIntent*/, false /*isLeOutput*/);
+ postBluetoothActiveDevice(device, bpci);
+ }
+ } break;
+ default:
+ // Not a valid profile to connect
+ Log.wtf(TAG, "Invalid profile! onBtProfileConnected");
+ break;
+ }
+ }
+
+ private void postBluetoothActiveDevice(
+ BluetoothDevice device, BluetoothProfileConnectionInfo bpci) {
+ AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
+ device, null, bpci, "mBluetoothProfileServiceListener");
+ AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
+ data, device, BluetoothProfile.STATE_CONNECTED);
+ mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
}
/*package*/ synchronized boolean isProfilePoxyConnected(int profile) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index d64b6c2..8dc560b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -20,6 +20,9 @@
import android.content.Context;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.AcquiredInfoAndVendorCode;
+import android.hardware.biometrics.fingerprint.EnrollmentProgressStep;
+import android.hardware.biometrics.fingerprint.NextEnrollment;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
@@ -46,6 +49,7 @@
class BiometricTestSessionImpl extends ITestSession.Stub {
private static final String TAG = "fp/aidl/BiometricTestSessionImpl";
+ private static final int VHAL_ENROLLMENT_ID = 9999;
@NonNull private final Context mContext;
private final int mSensorId;
@@ -140,8 +144,8 @@
super.setTestHalEnabled_enforcePermission();
- mProvider.setTestHalEnabled(enabled);
mSensor.setTestHalEnabled(enabled);
+ mProvider.setTestHalEnabled(enabled);
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@@ -157,10 +161,31 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void finishEnroll(int userId) {
+ public void finishEnroll(int userId) throws RemoteException {
super.finishEnroll_enforcePermission();
+ Slog.i(TAG, "finishEnroll(): useVhalForTesting=" + mProvider.useVhalForTesting());
+ if (mProvider.useVhalForTesting()) {
+ final AcquiredInfoAndVendorCode[] acquiredInfoAndVendorCodes =
+ {new AcquiredInfoAndVendorCode()};
+ final EnrollmentProgressStep[] enrollmentProgressSteps =
+ {new EnrollmentProgressStep(), new EnrollmentProgressStep()};
+ enrollmentProgressSteps[0].durationMs = 100;
+ enrollmentProgressSteps[0].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+ enrollmentProgressSteps[1].durationMs = 200;
+ enrollmentProgressSteps[1].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+
+ final NextEnrollment nextEnrollment = new NextEnrollment();
+ nextEnrollment.id = VHAL_ENROLLMENT_ID;
+ nextEnrollment.progressSteps = enrollmentProgressSteps;
+ nextEnrollment.result = true;
+ mProvider.getVhal().setNextEnrollment(nextEnrollment);
+ mProvider.simulateVhalFingerDown(userId, mSensorId);
+ return;
+ }
+
+ //TODO (b341889971): delete the following lines when b/341889971 is resolved
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -173,11 +198,18 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void acceptAuthentication(int userId) {
+ public void acceptAuthentication(int userId) throws RemoteException {
// Fake authentication with any of the existing fingers
super.acceptAuthentication_enforcePermission();
+ if (mProvider.useVhalForTesting()) {
+ mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID);
+ mProvider.simulateVhalFingerDown(userId, mSensorId);
+ return;
+ }
+
+ //TODO (b341889971): delete the following lines when b/341889971 is resolved
List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (fingerprints.isEmpty()) {
@@ -191,10 +223,17 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void rejectAuthentication(int userId) {
+ public void rejectAuthentication(int userId) throws RemoteException {
super.rejectAuthentication_enforcePermission();
+ if (mProvider.useVhalForTesting()) {
+ mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID + 1);
+ mProvider.simulateVhalFingerDown(userId, mSensorId);
+ return;
+ }
+
+ //TODO (b341889971): delete the following lines when b/341889971 is resolved
mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
@@ -220,11 +259,17 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void cleanupInternalState(int userId) {
+ public void cleanupInternalState(int userId) throws RemoteException {
super.cleanupInternalState_enforcePermission();
Slog.d(TAG, "cleanupInternalState: " + userId);
+
+ if (mProvider.useVhalForTesting()) {
+ Slog.i(TAG, "cleanup virtualhal configurations");
+ mProvider.getVhal().resetConfigurations(); //setEnrollments(new int[]{});
+ }
+
mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -248,4 +293,4 @@
}
});
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c0dcd49..1bddb83b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -890,7 +890,13 @@
}
void setTestHalEnabled(boolean enabled) {
+ final boolean changed = enabled != mTestHalEnabled;
mTestHalEnabled = enabled;
+ Slog.i(getTag(), "setTestHalEnabled(): useVhalForTesting=" + Flags.useVhalForTesting()
+ + "mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
+ if (changed && useVhalForTesting()) {
+ getHalInstance();
+ }
}
public boolean getTestHalEnabled() {
@@ -982,7 +988,7 @@
if (mVhal == null && useVhalForTesting()) {
mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
if (mVhal == null) {
- Slog.e(getTag(), "Unable to get virtual hal interface");
+ Slog.e(getTag(), "Unable to get fingerprint virtualhal interface");
}
}
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index fa8299b..38eb416 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -28,9 +28,9 @@
* Calls into SurfaceFlinger for Display creation and deletion.
*/
public class DisplayControl {
- private static native IBinder nativeCreateDisplay(String name, boolean secure,
+ private static native IBinder nativeCreateVirtualDisplay(String name, boolean secure,
String uniqueId, float requestedRefreshRate);
- private static native void nativeDestroyDisplay(IBinder displayToken);
+ private static native void nativeDestroyVirtualDisplay(IBinder displayToken);
private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
private static native long[] nativeGetPhysicalDisplayIds();
private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
@@ -41,21 +41,21 @@
private static native boolean nativeGetHdrOutputConversionSupport();
/**
- * Create a display in SurfaceFlinger.
+ * Create a virtual display in SurfaceFlinger.
*
- * @param name The name of the display.
+ * @param name The name of the virtual display.
* @param secure Whether this display is secure.
* @return The token reference for the display in SurfaceFlinger.
*/
- public static IBinder createDisplay(String name, boolean secure) {
+ public static IBinder createVirtualDisplay(String name, boolean secure) {
Objects.requireNonNull(name, "name must not be null");
- return nativeCreateDisplay(name, secure, "", 0.0f);
+ return nativeCreateVirtualDisplay(name, secure, "", 0.0f);
}
/**
- * Create a display in SurfaceFlinger.
+ * Create a virtual display in SurfaceFlinger.
*
- * @param name The name of the display.
+ * @param name The name of the virtual display.
* @param secure Whether this display is secure.
* @param uniqueId The unique ID for the display.
* @param requestedRefreshRate The requested refresh rate in frames per second.
@@ -65,23 +65,23 @@
* display is refreshed at the physical display refresh rate.
* @return The token reference for the display in SurfaceFlinger.
*/
- public static IBinder createDisplay(String name, boolean secure,
+ public static IBinder createVirtualDisplay(String name, boolean secure,
String uniqueId, float requestedRefreshRate) {
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(uniqueId, "uniqueId must not be null");
- return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate);
+ return nativeCreateVirtualDisplay(name, secure, uniqueId, requestedRefreshRate);
}
/**
- * Destroy a display in SurfaceFlinger.
+ * Destroy a virtual display in SurfaceFlinger.
*
- * @param displayToken The display token for the display to be destroyed.
+ * @param displayToken The display token for the virtual display to be destroyed.
*/
- public static void destroyDisplay(IBinder displayToken) {
+ public static void destroyVirtualDisplay(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
}
- nativeDestroyDisplay(displayToken);
+ nativeDestroyVirtualDisplay(displayToken);
}
/**
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 22ff2d0..eb76dcb 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -309,7 +309,7 @@
mSurface.release();
mSurface = null;
}
- DisplayControl.destroyDisplay(getDisplayTokenLocked());
+ DisplayControl.destroyVirtualDisplay(getDisplayTokenLocked());
}
@Override
@@ -467,7 +467,7 @@
public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
long presentationDeadlineNanos, int state) {
synchronized (getSyncRoot()) {
- IBinder displayToken = DisplayControl.createDisplay(mName, mFlags.mSecure);
+ IBinder displayToken = DisplayControl.createVirtualDisplay(mName, mFlags.mSecure);
mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
mFlags, state, surfaceTexture, mNumber) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a29e852..1a5c79f 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -94,12 +94,13 @@
@Override
public IBinder createDisplay(String name, boolean secure, String uniqueId,
float requestedRefreshRate) {
- return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate);
+ return DisplayControl.createVirtualDisplay(name, secure, uniqueId,
+ requestedRefreshRate);
}
@Override
public void destroyDisplay(IBinder displayToken) {
- DisplayControl.destroyDisplay(displayToken);
+ DisplayControl.destroyVirtualDisplay(displayToken);
}
}, featureFlags);
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index aa98cd8..607c5d6 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -392,9 +392,9 @@
float refreshRate = 60.0f; // TODO: get this for real
- String name = display.getFriendlyDisplayName();
- String address = display.getDeviceAddress();
- IBinder displayToken = DisplayControl.createDisplay(name, secure);
+ final String name = display.getFriendlyDisplayName();
+ final String address = display.getDeviceAddress();
+ IBinder displayToken = DisplayControl.createVirtualDisplay(name, secure);
mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
refreshRate, deviceFlags, address, surface);
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
@@ -631,7 +631,7 @@
mSurface.release();
mSurface = null;
}
- DisplayControl.destroyDisplay(getDisplayTokenLocked());
+ DisplayControl.destroyVirtualDisplay(getDisplayTokenLocked());
}
public void setNameLocked(String name) {
diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
index ad98b4a..a3b1a2d 100644
--- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
+++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
@@ -102,26 +102,33 @@
boolean touchExplorationEnabled) {
clearPendingInlineSuggestionsRequest();
mInlineSuggestionsRequestCallback = callback;
- final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked(
- mService.getSelectedMethodIdLocked());
- if (userId == mService.getCurrentImeUserIdLocked()
- && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
- mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
- requestInfo, callback, imi.getPackageName());
- if (mService.getCurMethodLocked() != null) {
- // In the normal case when the IME is connected, we can make the request here.
- performOnCreateInlineSuggestionsRequest();
- } else {
- // Otherwise, the next time the IME connection is established,
- // InputMethodBindingController.mMainConnection#onServiceConnected() will call
- // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
- if (DEBUG) {
- Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
- }
- }
- } else {
+ if (userId != mService.getCurrentImeUserIdLocked()) {
callback.onInlineSuggestionsUnsupported();
+ return;
+ }
+
+ // Note that current user ID is guaranteed to be userId.
+ final var imeId = mService.getSelectedMethodIdLocked();
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(userId).getMethodMap()
+ .get(imeId);
+ if (imi == null || !isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
+ callback.onInlineSuggestionsUnsupported();
+ return;
+ }
+
+ mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
+ requestInfo, callback, imi.getPackageName());
+ if (mService.getCurMethodLocked() != null) {
+ // In the normal case when the IME is connected, we can make the request here.
+ performOnCreateInlineSuggestionsRequest();
+ } else {
+ // Otherwise, the next time the IME connection is established,
+ // InputMethodBindingController.mMainConnection#onServiceConnected() will call
+ // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
+ if (DEBUG) {
+ Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7548b36..f45b82a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -491,6 +491,12 @@
*/
boolean mSystemReady;
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
+ return mUserDataRepository.getOrCreate(userId).mBindingController;
+ }
+
/**
* Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
* This is to be synchronized with the secure settings keyed with
@@ -507,8 +513,7 @@
@GuardedBy("ImfLock.class")
@Nullable
String getSelectedMethodIdLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getSelectedMethodId();
+ return getInputMethodBindingController(mCurrentUserId).getSelectedMethodId();
}
@GuardedBy("ImfLock.class")
@@ -594,8 +599,7 @@
@GuardedBy("ImfLock.class")
@Nullable
IBinder getCurTokenLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getCurToken();
+ return getInputMethodBindingController(mCurrentUserId).getCurToken();
}
/**
@@ -603,8 +607,7 @@
*/
@GuardedBy("ImfLock.class")
int getCurTokenDisplayIdLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getCurTokenDisplayId();
+ return getInputMethodBindingController(mCurrentUserId).getCurTokenDisplayId();
}
/**
@@ -620,8 +623,7 @@
@GuardedBy("ImfLock.class")
@Nullable
IInputMethodInvoker getCurMethodLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getCurMethod();
+ return getInputMethodBindingController(mCurrentUserId).getCurMethod();
}
/**
@@ -1127,6 +1129,11 @@
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
// Called on ActivityManager thread.
synchronized (ImfLock.class) {
+ if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
+ // In concurrent multi-user mode, we in general do not rely on the concept of
+ // current user.
+ return;
+ }
mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(),
/* clientToBeReset= */ null);
}
@@ -1269,7 +1276,15 @@
InputMethodSettingsRepository.initialize(mHandler, mContext);
AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
- mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+ final int currentUserId = mActivityManagerInternal.getCurrentUserId();
+
+ // For concurrent multi-user mode, we try to initialize mCurrentUserId with main
+ // user rather than the current user when possible.
+ mCurrentUserId = mExperimentalConcurrentMultiUserModeEnabled
+ ? MultiUserUtils.getFirstMainUserIdOrDefault(
+ mUserManagerInternal, currentUserId)
+ : currentUserId;
+
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
InputMethodManagerService.this);
@@ -1422,8 +1437,7 @@
// Note that in b/197848765 we want to see if we can keep the binding alive for better
// profile switching.
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
@@ -1643,8 +1657,7 @@
// Check if selected IME of current user supports handwriting.
if (userId == mCurrentUserId) {
- final var userData = mUserDataRepository.getOrCreate(userId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(userId);
return bindingController.supportsStylusHandwriting()
&& (!connectionless
|| bindingController.supportsConnectionlessStylusHandwriting());
@@ -1844,8 +1857,7 @@
// TODO(b/325515685): make binding controller user independent. Before this change, the
// following dependencies also need to be user independent: mCurClient, mBoundToMethod,
// getCurMethodLocked(), and mMenuController.
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
unbindClientReason);
mCurClient.mSessionRequested = false;
@@ -1925,8 +1937,7 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
@@ -2032,7 +2043,7 @@
@StartInputReason int startInputReason,
int unverifiedTargetSdkVersion,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher,
- @NonNull UserDataRepository.UserData userData) {
+ @NonNull InputMethodBindingController bindingController) {
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
@@ -2073,7 +2084,6 @@
final boolean connectionWasActive = mCurInputConnection != null;
// Bump up the sequence for this client and attach it.
- final var bindingController = userData.mBindingController;
bindingController.advanceSequenceNumber();
mCurClient = cs;
@@ -2133,7 +2143,7 @@
(startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
}
- InputBindResult bindResult = tryReuseConnectionLocked(userData, cs);
+ InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs);
if (bindResult != null) {
return bindResult;
}
@@ -2247,9 +2257,8 @@
@GuardedBy("ImfLock.class")
@Nullable
- private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
- @NonNull ClientState cs) {
- final var bindingController = userData.mBindingController;
+ private InputBindResult tryReuseConnectionLocked(
+ @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs) {
if (bindingController.hasMainConnection()) {
if (getCurMethodLocked() != null) {
// Return to client, and we will get back with it when
@@ -2631,8 +2640,9 @@
// When the IME switcher dialog is shown, the IME switcher button should be hidden.
if (mMenuController.getSwitchingDialogLocked() != null) return false;
// When we are switching IMEs, the IME switcher button should be hidden.
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- if (!Objects.equals(userData.mBindingController.getCurId(), getSelectedMethodIdLocked())) {
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ if (!Objects.equals(bindingController.getCurId(),
+ bindingController.getSelectedMethodId())) {
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
@@ -2794,8 +2804,7 @@
} else {
vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
}
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var curId = userData.mBindingController.getCurId();
+ final var curId = getInputMethodBindingController(mCurrentUserId).getCurId();
if (mMenuController.getSwitchingDialogLocked() != null
|| !Objects.equals(curId, getSelectedMethodIdLocked())) {
// When the IME switcher dialog is shown, or we are switching IMEs,
@@ -2856,8 +2865,7 @@
id = imi.getId();
settings.putSelectedInputMethod(id);
}
- final var userData = mUserDataRepository.getOrCreate(userId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(userId);
bindingController.setSelectedMethodId(id);
}
@@ -3033,8 +3041,7 @@
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.setSelectedMethodId(id);
+ getInputMethodBindingController(mCurrentUserId).setSelectedMethodId(id);
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3089,8 +3096,8 @@
@Nullable String delegatorPackageName,
@NonNull IConnectionlessHandwritingCallback callback) {
synchronized (ImfLock.class) {
- final var userData = mUserDataRepository.getOrCreate(userId);
- if (!userData.mBindingController.supportsConnectionlessStylusHandwriting()) {
+ final var bindingController = getInputMethodBindingController(userId);
+ if (!bindingController.supportsConnectionlessStylusHandwriting()) {
Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
try {
callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
@@ -3173,8 +3180,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- if (!userData.mBindingController.supportsStylusHandwriting()) {
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ if (!bindingController.supportsStylusHandwriting()) {
Slog.w(TAG,
"Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
return false;
@@ -3357,8 +3364,8 @@
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.setCurrentMethodVisible();
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ bindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
final boolean readyToDispatchToIme;
@@ -3466,8 +3473,8 @@
} else {
ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
}
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.setCurrentMethodNotVisible();
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ bindingController.setCurrentMethodNotVisible();
mVisibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3535,8 +3542,7 @@
"InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
final InputBindResult result;
synchronized (ImfLock.class) {
- final var userData = mUserDataRepository.getOrCreate(userId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(userId);
// If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
return new InputBindResult(
@@ -3553,7 +3559,8 @@
final long ident = Binder.clearCallingIdentity();
try {
// Verify if IMMS is in the process of switching user.
- if (mUserSwitchHandlerTask != null) {
+ if (!mExperimentalConcurrentMultiUserModeEnabled
+ && mUserSwitchHandlerTask != null) {
// There is already an on-going pending user switch task.
final int nextUserId = mUserSwitchHandlerTask.mToUserId;
if (userId == nextUserId) {
@@ -3607,7 +3614,7 @@
}
// Verify if caller is a background user.
- if (userId != mCurrentUserId) {
+ if (!mExperimentalConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
if (ArrayUtils.contains(
mUserManagerInternal.getProfileIds(mCurrentUserId, false),
userId)) {
@@ -3635,7 +3642,7 @@
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userData, imeDispatcher, cs);
+ unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3663,7 +3670,7 @@
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData,
+ int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
@@ -3676,7 +3683,7 @@
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
- + " userData=" + userData
+ + " bindingController=" + bindingController
+ " imeDispatcher=" + imeDispatcher
+ " cs=" + cs);
}
@@ -3705,15 +3712,16 @@
if (editorInfo != null) {
return startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData);
+ startInputReason, unverifiedTargetSdkVersion, imeDispatcher,
+ bindingController);
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
null, null, null, null, -1, false);
}
- mImeBindingState = new ImeBindingState(userData.mUserId, windowToken, softInputMode, cs,
- editorInfo);
+ mImeBindingState = new ImeBindingState(bindingController.mUserId, windowToken,
+ softInputMode, cs, editorInfo);
mFocusedWindowPerceptible.put(windowToken, true);
// We want to start input before showing the IME, but after closing
@@ -3738,7 +3746,7 @@
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher, userData);
+ imeDispatcher, bindingController);
didStart = true;
}
break;
@@ -3753,7 +3761,7 @@
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
- userData.mBindingController.unbindCurrentMethod();
+ bindingController.unbindCurrentMethod();
}
}
}
@@ -3762,7 +3770,7 @@
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher, userData);
+ imeDispatcher, bindingController);
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
@@ -3803,8 +3811,7 @@
if (mCurrentUserId != UserHandle.getUserId(uid)) {
return false;
}
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var curIntent = userData.mBindingController.getCurIntent();
+ final var curIntent = getInputMethodBindingController(mCurrentUserId).getCurIntent();
if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) {
return true;
@@ -4213,8 +4220,7 @@
mStylusIds.add(deviceId);
// a new Stylus is detected. If IME supports handwriting, and we don't have
// handwriting initialized, lets do it now.
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
if (!mHwController.getCurrentRequestId().isPresent()
&& bindingController.supportsStylusHandwriting()) {
scheduleResetStylusHandwriting();
@@ -4395,8 +4401,7 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
@@ -4786,8 +4791,7 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
if (bindingController.supportsStylusHandwriting()
&& getCurMethodLocked() != null && hasSupportedStylusLocked()) {
Slog.d(TAG, "Initializing Handwriting Spy");
@@ -4813,8 +4817,7 @@
if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
return true;
}
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
@@ -4870,8 +4873,7 @@
return;
}
// TODO(b/325515685): user data must be retrieved by a userId parameter
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
bindingController.getCurMethodUid())) {
// Handle IME visibility when interactive changed before finishing the input to
@@ -5096,8 +5098,7 @@
@GuardedBy("ImfLock.class")
void sendOnNavButtonFlagsChangedLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
@@ -5584,8 +5585,7 @@
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5620,8 +5620,7 @@
public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
if (DEBUG) {
@@ -5853,8 +5852,7 @@
p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(mCurrentUserId);
p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
@@ -6376,8 +6374,7 @@
if (userId == mCurrentUserId) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
- final var userData = mUserDataRepository.getOrCreate(userId);
- final var bindingController = userData.mBindingController;
+ final var bindingController = getInputMethodBindingController(userId);
bindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
diff --git a/services/core/java/com/android/server/inputmethod/MultiUserUtils.java b/services/core/java/com/android/server/inputmethod/MultiUserUtils.java
new file mode 100644
index 0000000..8e188da
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/MultiUserUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+
+import com.android.server.pm.UserManagerInternal;
+
+final class MultiUserUtils {
+ /**
+ * Not intended to be instantiated.
+ */
+ private MultiUserUtils() {
+ }
+
+ /**
+ * Return the first user ID (a user has {@link android.content.pm.UserInfo#FLAG_MAIN} if
+ * available). Otherwise, return the given default value.
+ *
+ * @param userManagerInternal {@link UserManagerInternal} to be used to query about users
+ * @param defaultValue a user ID that will be returned when there is no main user
+ * @return The first main user ID
+ */
+ @AnyThread
+ @UserIdInt
+ static int getFirstMainUserIdOrDefault(@NonNull UserManagerInternal userManagerInternal,
+ @UserIdInt int defaultValue) {
+ final int[] userIds = userManagerInternal.getUserIds();
+ if (userIds != null) {
+ for (int userId : userIds) {
+ final var userInfo = userManagerInternal.getUserInfo(userId);
+ if (userInfo.isMain()) {
+ return userId;
+ }
+ }
+ }
+ return defaultValue;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index dbdb155..b14702d 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -737,12 +737,13 @@
!mUserManager.isQuietModeEnabled(userHandle)) {
// Only show notifications for managed profiles once their parent
// user is unlocked.
- showEncryptionNotificationForProfile(userHandle, reason);
+ showEncryptionNotificationForProfile(userHandle, parent.getUserHandle(), reason);
}
}
}
- private void showEncryptionNotificationForProfile(UserHandle user, String reason) {
+ private void showEncryptionNotificationForProfile(UserHandle user, UserHandle parent,
+ String reason) {
CharSequence title = getEncryptionNotificationTitle();
CharSequence message = getEncryptionNotificationMessage();
CharSequence detail = getEncryptionNotificationDetail();
@@ -759,8 +760,15 @@
unlockIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent intent;
+ if (android.app.admin.flags.Flags.hsumUnlockNotificationFix()) {
+ intent = PendingIntent.getActivityAsUser(mContext, 0, unlockIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ null, parent);
+ } else {
+ intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ }
Slogf.d(TAG, "Showing encryption notification for user %d; reason: %s",
user.getIdentifier(), reason);
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index a20de31..bea71dc 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.annotation.NonNull;
import android.app.ActivityThread;
import android.content.Context;
import android.media.MediaMetadata;
@@ -247,7 +248,7 @@
}
@Override
- public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
+ public void onAudioInfoChanged(@NonNull MediaController.PlaybackInfo info) {
mWriter.println("onAudioInfoChanged " + info);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 71a7d0d..f07b710 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -17,6 +17,7 @@
package com.android.server.os;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
import android.Manifest;
import android.annotation.NonNull;
@@ -31,6 +32,7 @@
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
+import android.os.Build;
import android.os.Environment;
import android.os.IDumpstate;
import android.os.IDumpstateListener;
@@ -69,12 +71,14 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -98,6 +102,9 @@
private static final String BUGREPORT_SERVICE = "bugreportd";
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
+ private static final long DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS =
+ TimeUnit.MINUTES.toMillis(2);
+
private final Object mLock = new Object();
private final Injector mInjector;
private final Context mContext;
@@ -132,6 +139,10 @@
private ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
new ArrayMap<>();
+ // Map of <CallerPackage, Pair<TimestampOfLastConsent, skipConsentForFullReport>>
+ @GuardedBy("mLock")
+ private Map<String, Pair<Long, Boolean>> mConsentGranted = new HashMap<>();
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@GuardedBy("mLock")
final Set<String> mBugreportFilesToPersist = new HashSet<>();
@@ -238,6 +249,64 @@
}
}
+ /**
+ * Logs an entry with a timestamp of a consent being granted by the user to the calling
+ * {@code packageName}.
+ */
+ void logConsentGrantedForCaller(
+ String packageName, boolean consentGranted, boolean isDeferredReport) {
+ if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) {
+ return;
+ }
+ synchronized (mLock) {
+ // Adds an entry with the timestamp of the consent being granted by the user, and
+ // whether the consent can be skipped for a full bugreport, because a single
+ // consent can be used for multiple deferred reports but only one full report.
+ if (consentGranted) {
+ mConsentGranted.put(packageName, new Pair<>(
+ System.currentTimeMillis(),
+ isDeferredReport));
+ } else if (!isDeferredReport) {
+ if (!mConsentGranted.containsKey(packageName)) {
+ Slog.e(TAG, "Previous consent from package: " + packageName + " should"
+ + "have been logged.");
+ return;
+ }
+ mConsentGranted.put(packageName, new Pair<>(
+ mConsentGranted.get(packageName).first,
+ /* second = */ false
+ ));
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if user consent be skippeb because a previous consens has been
+ * granted to the caller within the allowed time period.
+ */
+ boolean canSkipConsentScreen(String packageName, boolean isFullReport) {
+ if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) {
+ return false;
+ }
+ synchronized (mLock) {
+ if (!mConsentGranted.containsKey(packageName)) {
+ return false;
+ }
+ long currentTime = System.currentTimeMillis();
+ long consentGrantedTime = mConsentGranted.get(packageName).first;
+ if (consentGrantedTime + DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS
+ < currentTime) {
+ mConsentGranted.remove(packageName);
+ return false;
+ }
+ boolean skipConsentForFullReport = mConsentGranted.get(packageName).second;
+ if (isFullReport && !skipConsentForFullReport) {
+ return false;
+ }
+ return true;
+ }
+ }
+
private void addBugreportMapping(Pair<Integer, String> caller, String bugreportFile) {
synchronized (mLock) {
if (!mBugreportFiles.containsKey(caller)) {
@@ -418,7 +487,7 @@
public void startBugreport(int callingUidUnused, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
int bugreportMode, int bugreportFlags, IDumpstateListener listener,
- boolean isScreenshotRequested) {
+ boolean isScreenshotRequested, boolean skipUserConsentUnused) {
Objects.requireNonNull(callingPackage);
Objects.requireNonNull(bugreportFd);
Objects.requireNonNull(listener);
@@ -509,7 +578,8 @@
@RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
FileDescriptor bugreportFd, String bugreportFile,
- boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
+ boolean keepBugreportOnRetrievalUnused, boolean skipUserConsentUnused,
+ IDumpstateListener listener) {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
@@ -540,9 +610,13 @@
return;
}
+ boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen(
+ callingPackage, /* isFullReport = */ false);
+
// Wrap the listener so we can intercept binder events directly.
DumpstateListener myListener = new DumpstateListener(listener, ds,
- new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+ new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true,
+ !skipUserConsent, /* isDeferredReport = */ true);
boolean keepBugreportOnRetrieval = false;
if (onboardingBugreportV2Enabled()) {
@@ -553,7 +627,7 @@
setCurrentDumpstateListenerLocked(myListener);
try {
ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
- bugreportFile, keepBugreportOnRetrieval, myListener);
+ bugreportFile, keepBugreportOnRetrieval, skipUserConsent, myListener);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in retrieveBugreport", e);
}
@@ -754,7 +828,7 @@
}
}
- boolean reportFinishedFile =
+ boolean isDeferredConsentReport =
(bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
boolean keepBugreportOnRetrieval =
@@ -766,14 +840,17 @@
reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
return;
}
-
+ boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen(
+ callingPackage, !isDeferredConsentReport);
DumpstateListener myListener = new DumpstateListener(listener, ds,
- new Pair<>(callingUid, callingPackage), reportFinishedFile,
- keepBugreportOnRetrieval);
+ new Pair<>(callingUid, callingPackage),
+ /* reportFinishedFile = */ isDeferredConsentReport, keepBugreportOnRetrieval,
+ !isDeferredConsentReport && !skipUserConsent,
+ isDeferredConsentReport);
setCurrentDumpstateListenerLocked(myListener);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
- bugreportFlags, myListener, isScreenshotRequested);
+ bugreportFlags, myListener, isScreenshotRequested, skipUserConsent);
} catch (RemoteException e) {
// dumpstate service is already started now. We need to kill it to manage the
// lifecycle correctly. If we don't subsequent callers will get
@@ -930,14 +1007,21 @@
private boolean mDone;
private boolean mKeepBugreportOnRetrieval;
+ private boolean mConsentGranted;
+
+ private boolean mIsDeferredReport;
+
DumpstateListener(IDumpstateListener listener, IDumpstate ds,
- Pair<Integer, String> caller, boolean reportFinishedFile) {
- this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false);
+ Pair<Integer, String> caller, boolean reportFinishedFile,
+ boolean consentGranted, boolean isDeferredReport) {
+ this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false,
+ consentGranted, isDeferredReport);
}
DumpstateListener(IDumpstateListener listener, IDumpstate ds,
Pair<Integer, String> caller, boolean reportFinishedFile,
- boolean keepBugreportOnRetrieval) {
+ boolean keepBugreportOnRetrieval, boolean consentGranted,
+ boolean isDeferredReport) {
if (DEBUG) {
Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
}
@@ -946,6 +1030,8 @@
mCaller = caller;
mReportFinishedFile = reportFinishedFile;
mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
+ mConsentGranted = consentGranted;
+ mIsDeferredReport = isDeferredReport;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -985,6 +1071,8 @@
} else if (DEBUG) {
Slog.d(TAG, "Not reporting finished file");
}
+ mBugreportFileManager.logConsentGrantedForCaller(
+ mCaller.second, mConsentGranted, mIsDeferredReport);
mListener.onFinished(bugreportFile);
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 472f228..6cfa09f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -511,14 +511,13 @@
// metadata file on the system image. Do not reset the path and source if this is the
// case.
if (pkgSetting.getAppMetadataFilePath() == null) {
- File dir = new File(pkg.getPath());
+ String dir = pkg.getPath();
if (pkgSetting.isSystem()) {
- dir = new File(Environment.getDataDirectory(),
- "app-metadata/" + pkg.getPackageName());
+ dir = Environment.getDataDirectoryPath() + "/app-metadata/" + pkg.getPackageName();
}
- File appMetadataFile = new File(dir, APP_METADATA_FILE_NAME);
- if (appMetadataFile.exists()) {
- pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+ String appMetadataFilePath = dir + "/" + APP_METADATA_FILE_NAME;
+ if (request.hasAppMetadataFile()) {
+ pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
if (Flags.aslInApkAppMetadataSource()) {
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER);
}
@@ -526,7 +525,7 @@
Map<String, PackageManager.Property> properties = pkg.getProperties();
if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
// ASL file extraction is done in post-install
- pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+ pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
}
}
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 6d38517..8f51e36 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -167,6 +167,8 @@
private int mInstallerUidForInstallExisting = INVALID_UID;
+ private final boolean mHasAppMetadataFileFromInstaller;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -185,6 +187,7 @@
mSessionId = params.mSessionId;
mRequireUserAction = params.mRequireUserAction;
mPreVerifiedDomains = params.mPreVerifiedDomains;
+ mHasAppMetadataFileFromInstaller = params.mHasAppMetadataFile;
}
// Install existing package as user
@@ -203,6 +206,7 @@
mAppId = appId;
mInstallerUidForInstallExisting = installerUid;
mSystem = isSystem;
+ mHasAppMetadataFileFromInstaller = false;
}
// addForInit
@@ -224,6 +228,7 @@
mSessionId = -1;
mRequireUserAction = USER_ACTION_UNSPECIFIED;
mDisabledPs = disabledPs;
+ mHasAppMetadataFileFromInstaller = false;
}
@Nullable
@@ -371,6 +376,10 @@
return PackageInstallerSession.isArchivedInstallation(getInstallFlags());
}
+ public boolean hasAppMetadataFile() {
+ return mHasAppMetadataFileFromInstaller;
+ }
+
@Nullable
public String getRemovedPackage() {
return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null;
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 4cbd3ad..b06c7cb 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -101,6 +101,7 @@
final boolean mApplicationEnabledSettingPersistent;
@Nullable
final DomainSet mPreVerifiedDomains;
+ final boolean mHasAppMetadataFile;
// For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -134,12 +135,14 @@
mRequireUserAction = USER_ACTION_UNSPECIFIED;
mApplicationEnabledSettingPersistent = false;
mPreVerifiedDomains = null;
+ mHasAppMetadataFile = false;
}
InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
- PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) {
+ PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm,
+ boolean hasAppMetadatafile) {
mPm = pm;
mUser = user;
mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -168,6 +171,7 @@
mRequireUserAction = sessionParams.requireUserAction;
mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
mPreVerifiedDomains = preVerifiedDomains;
+ mHasAppMetadataFile = hasAppMetadatafile;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 80a5f3a..57f6d27 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -601,6 +601,9 @@
@GuardedBy("mLock")
private String mSessionErrorMessage;
+ @GuardedBy("mLock")
+ private boolean mHasAppMetadataFile = false;
+
@Nullable
final StagedSession mStagedSession;
@@ -1814,7 +1817,7 @@
assertCallerIsOwnerOrRoot();
synchronized (mLock) {
assertPreparedAndNotCommittedOrDestroyedLocked("getAppMetadataFd");
- if (!getStagedAppMetadataFile().exists()) {
+ if (!mHasAppMetadataFile) {
return null;
}
try {
@@ -1827,9 +1830,11 @@
@Override
public void removeAppMetadata() {
- File file = getStagedAppMetadataFile();
- if (file.exists()) {
- file.delete();
+ synchronized (mLock) {
+ if (mHasAppMetadataFile) {
+ getStagedAppMetadataFile().delete();
+ mHasAppMetadataFile = false;
+ }
}
}
@@ -1850,8 +1855,12 @@
assertPreparedAndNotSealedLocked("openWriteAppMetadata");
}
try {
- return doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0,
+ ParcelFileDescriptor fd = doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0,
/* lengthBytes= */ -1, null);
+ synchronized (mLock) {
+ mHasAppMetadataFile = true;
+ }
+ return fd;
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
@@ -2145,18 +2154,21 @@
}
}
- File appMetadataFile = getStagedAppMetadataFile();
- if (appMetadataFile.exists()) {
- long sizeLimit = getAppMetadataSizeLimit();
- if (appMetadataFile.length() > sizeLimit) {
- appMetadataFile.delete();
- throw new IllegalArgumentException(
- "App metadata size exceeds the maximum allowed limit of " + sizeLimit);
- }
- if (isIncrementalInstallation()) {
- // Incremental requires stageDir to be empty so move the app metadata file to a
- // temporary location and move back after commit.
- appMetadataFile.renameTo(getTmpAppMetadataFile());
+ synchronized (mLock) {
+ if (mHasAppMetadataFile) {
+ File appMetadataFile = getStagedAppMetadataFile();
+ long sizeLimit = getAppMetadataSizeLimit();
+ if (appMetadataFile.length() > sizeLimit) {
+ appMetadataFile.delete();
+ mHasAppMetadataFile = false;
+ throw new IllegalArgumentException(
+ "App metadata size exceeds the maximum allowed limit of " + sizeLimit);
+ }
+ if (isIncrementalInstallation()) {
+ // Incremental requires stageDir to be empty so move the app metadata file to a
+ // temporary location and move back after commit.
+ appMetadataFile.renameTo(getTmpAppMetadataFile());
+ }
}
}
@@ -3207,7 +3219,8 @@
synchronized (mLock) {
return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
- user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm);
+ user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm,
+ mHasAppMetadataFile);
}
}
@@ -3445,9 +3458,14 @@
}
}
+ if (mHasAppMetadataFile && !getStagedAppMetadataFile().exists()) {
+ throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "App metadata file expected but not found in " + stageDir.getAbsolutePath());
+ }
+
final List<ApkLite> addedFiles = getAddedApkLitesLocked();
if (addedFiles.isEmpty()
- && (removeSplitList.size() == 0 || getStagedAppMetadataFile().exists())) {
+ && (removeSplitList.size() == 0 || mHasAppMetadataFile)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
stageDir.getAbsolutePath()));
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index ad2c3e8..3579246 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -225,7 +225,7 @@
@NonNull TimeConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
Objects.requireNonNull(requestedConfiguration);
- TimeCapabilitiesAndConfig capabilitiesAndConfig = getCurrentUserConfigurationInternal()
+ TimeCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
.createCapabilitiesAndConfig(bypassUserPolicyChecks);
TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
TimeConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index dd3d512..80f1125 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -150,7 +150,7 @@
Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl);
// compute the crop on portrait at the center of the landscape crop
- crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD);
+ crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, ADD);
// add some parallax (until the border of the landscape crop without parallax)
if (rtl) {
@@ -160,7 +160,7 @@
}
}
- return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+ return getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD);
}
// If any suggested crop is invalid, fallback to case 1
@@ -176,7 +176,7 @@
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
- return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
+ return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, ADD);
}
// Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -188,7 +188,7 @@
if (suggestedCrop != null) {
// only keep the visible part (without parallax)
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
- return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, BALANCE);
}
// Case 4: if the device is a foldable, if we're looking for a folded orientation and have
@@ -200,13 +200,13 @@
// compute the visible part (without parallax) of the unfolded screen
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
// compute the folded crop, at the center of the crop of the unfolded screen
- Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, REMOVE);
// if we removed some width, add it back to add a parallax effect
if (res.width() < adjustedCrop.width()) {
if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
else res.right = Math.max(res.right, adjustedCrop.right);
// use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
- res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
+ res = getAdjustedCrop(res, bitmapSize, displaySize, true, ADD);
}
return res;
}
@@ -220,7 +220,7 @@
if (suggestedCrop != null) {
// only keep the visible part (without parallax)
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
- return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD);
+ return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, ADD);
}
// Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
@@ -255,7 +255,7 @@
@VisibleForTesting
static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
if (displaySize == null) return crop;
- Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+ Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD);
// only keep the visible part (without parallax)
float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y;
int widthToRemove = (int) (adjustedCrop.width()
@@ -272,7 +272,7 @@
* Adjust a given crop:
* <ul>
* <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX},
- * by removing content from the right (or left if RTL layout) if necessary.
+ * by removing content from both sides if necessary.
* <li>If parallax = false, make sure we do not have additional width for parallax. If we
* have additional width for parallax, remove half of the additional width on both sides.
* <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop
@@ -282,7 +282,7 @@
*/
@VisibleForTesting
static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
- boolean parallax, boolean rtl, int mode) {
+ boolean parallax, int mode) {
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
@@ -297,8 +297,7 @@
Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom);
Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x);
Point rotatedScreen = new Point(screenSize.y, screenSize.x);
- Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl,
- mode);
+ Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, mode);
int resultLeft = rect.top;
int resultRight = resultLeft + rect.height();
int resultTop = rotatedBitmap.x - rect.right;
@@ -318,9 +317,8 @@
// total surface of W * H. In other words it is the width to add to get the desired
// aspect ratio R, while preserving the total number of pixels W * H.
int widthToAdd = mode == REMOVE ? 0
- : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
- : (int) (0.5 - crop.width()
- + Math.sqrt(crop.width() * crop.height() * screenRatio));
+ : mode == ADD ? (int) (crop.height() * screenRatio - crop.width())
+ : (int) (-crop.width() + Math.sqrt(crop.width() * crop.height() * screenRatio));
int availableWidth = bitmapSize.x - crop.width();
if (availableWidth >= widthToAdd) {
int widthToAddLeft = widthToAdd / 2;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d053bbb..2f6e07c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -660,6 +660,8 @@
private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
+ private final boolean mOptOutEdgeToEdge;
+
private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
@@ -2179,9 +2181,12 @@
|| ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
mStyleFillsParent = mOccludesParent;
noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ mOptOutEdgeToEdge = ent.array.getBoolean(
+ R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
} else {
mStyleFillsParent = mOccludesParent = true;
noDisplay = false;
+ mOptOutEdgeToEdge = false;
}
if (options != null) {
@@ -8710,9 +8715,9 @@
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
- if (!mResolveConfigHint.mUseOverrideInsetsForConfig
- || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()
- || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) {
+ if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
+ || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
+ || rotation == ROTATION_UNDEFINED)) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 62931bb..f7910b0 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -105,6 +106,7 @@
static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent";
static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult";
static final String AUTO_OPT_IN_SAME_UID = "sameUid";
+ static final String AUTO_OPT_IN_COMPAT = "compatibility";
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@@ -303,6 +305,10 @@
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
mAutoOptInCaller = false;
+ } else if (realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_COMPAT) {
+ mAutoOptInReason = AUTO_OPT_IN_COMPAT;
+ mAutoOptInCaller = false;
} else {
mAutoOptInReason = null;
mAutoOptInCaller = false;
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 42ca7b4..16fcb09 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -348,6 +348,9 @@
+ bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
return false;
}
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+ bitmap.recycle();
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
try {
@@ -365,8 +368,8 @@
}
final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
- (int) (bitmap.getWidth() * mPersistInfoProvider.lowResScaleFactor()),
- (int) (bitmap.getHeight() * mPersistInfoProvider.lowResScaleFactor()),
+ (int) (width * mPersistInfoProvider.lowResScaleFactor()),
+ (int) (height * mPersistInfoProvider.lowResScaleFactor()),
true /* filter */);
swBitmap.recycle();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ce53290..2dc439d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -492,6 +492,27 @@
return false;
}
+ /** Returns {@code true} if the display contains a transient-launch transition. */
+ boolean hasTransientLaunch(@NonNull DisplayContent dc) {
+ if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch()
+ && mCollectingTransition.isOnDisplay(dc)) {
+ return true;
+ }
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ final Transition transition = mWaitingTransitions.get(i);
+ if (transition.hasTransientLaunch() && transition.isOnDisplay(dc)) {
+ return true;
+ }
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ final Transition transition = mPlayingTransitions.get(i);
+ if (transition.hasTransientLaunch() && transition.isOnDisplay(dc)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean isTransientHide(@NonNull Task task) {
if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
return true;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 65e1761..3e43f5a 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -165,7 +165,7 @@
|| (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
}
} else if (w.hasWallpaper() && mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
- && w.mTransitionController.isTransitionOnDisplay(mDisplayContent)) {
+ && w.mTransitionController.hasTransientLaunch(mDisplayContent)) {
// If we have no candidates at all, notification shade is allowed to be the target
// of last resort even if it has not been made visible yet.
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found keyguard as wallpaper target: " + w);
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index 22c0f73..6613a25 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -23,20 +23,22 @@
namespace android {
-static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
- jstring uniqueIdStr, jfloat requestedRefreshRate) {
+static jobject nativeCreateVirtualDisplay(JNIEnv* env, jclass clazz, jstring nameObj,
+ jboolean secure, jstring uniqueIdStr,
+ jfloat requestedRefreshRate) {
const ScopedUtfChars name(env, nameObj);
const ScopedUtfChars uniqueId(env, uniqueIdStr);
- sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
- std::string(uniqueId.c_str()),
- requestedRefreshRate));
+ sp<IBinder> token(SurfaceComposerClient::createVirtualDisplay(std::string(name.c_str()),
+ bool(secure),
+ std::string(uniqueId.c_str()),
+ requestedRefreshRate));
return javaObjectForIBinder(env, token);
}
-static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static void nativeDestroyVirtualDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
if (token == NULL) return;
- SurfaceComposerClient::destroyDisplay(token);
+ SurfaceComposerClient::destroyVirtualDisplay(token);
}
static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
@@ -180,10 +182,10 @@
static const JNINativeMethod sDisplayMethods[] = {
// clang-format off
- {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
- (void*)nativeCreateDisplay },
- {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
- (void*)nativeDestroyDisplay },
+ {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
+ (void*)nativeCreateVirtualDisplay },
+ {"nativeDestroyVirtualDisplay", "(Landroid/os/IBinder;)V",
+ (void*)nativeDestroyVirtualDisplay },
{"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
(void*)nativeOverrideHdrTypes },
{"nativeGetPhysicalDisplayIds", "()[J",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4c746a9..b19de18 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -601,7 +601,7 @@
// Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
// Received data: ['inputPort1', '1', 'inputPort2', '2']
// So we unpack accordingly here.
- outConfig->portAssociations.clear();
+ outConfig->inputPortToDisplayPortAssociations.clear();
jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
gServiceClassInfo.getInputPortAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
@@ -618,16 +618,16 @@
displayPortStr.c_str());
continue;
}
- outConfig->portAssociations.insert({inputPort, displayPort});
+ outConfig->inputPortToDisplayPortAssociations.insert({inputPort, displayPort});
}
env->DeleteLocalRef(portAssociations);
}
- outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+ outConfig->inputPortToDisplayUniqueIdAssociations = readMapFromInterleavedJavaArray<
std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
"getInputUniqueIdAssociationsByPort");
- outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
+ outConfig->inputDeviceDescriptorToDisplayUniqueIdAssociations = readMapFromInterleavedJavaArray<
std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
"getInputUniqueIdAssociationsByDescriptor");
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3b25cb1..5f395c56 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -37,6 +37,7 @@
import android.app.ActivityManagerInternal;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerGlobal;
@@ -207,6 +208,16 @@
when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
.thenReturn(new int[] {0});
when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0});
+ when(mMockUserManagerInternal.getUserInfo(anyInt())).thenAnswer(invocation -> {
+ final int userId = invocation.getArgument(0);
+ if (userId == 0) {
+ new UserInfo(userId, "main",
+ UserInfo.FLAG_PRIMARY | UserInfo.FLAG_MAIN | UserInfo.FLAG_SYSTEM);
+ }
+ // TODO(b/315348827): Update mock for multi-user scenarios.
+ throw new UnsupportedOperationException(
+ "Please mock #getUserInfo for userId=" + userId);
+ });
when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
when(mMockActivityManagerInternal.getCurrentUserId()).thenReturn(mCallingUserId);
when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ea7bb8b..a738acb 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -105,6 +105,7 @@
":PackageParserTestApp5",
":PackageParserTestApp6",
":PackageParserTestApp7",
+ ":PackageParserTestApp8",
],
resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index a0e0e1e..5da202f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -101,6 +101,7 @@
import com.android.internal.pm.pkg.component.ParsedUsesPermission;
import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -126,6 +127,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -154,6 +156,7 @@
private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
private static final String TEST_APP7_APK = "PackageParserTestApp7.apk";
+ private static final String TEST_APP8_APK = "PackageParserTestApp8.apk";
private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
@Before
@@ -814,6 +817,39 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING)
+ public void testParseWithFeatureFlagAttributes() throws Exception {
+ final File testFile = extractFile(TEST_APP8_APK);
+ try (PackageParser2 parser = new TestPackageParser2()) {
+ Map<String, Boolean> flagValues = new HashMap<>();
+ flagValues.put("my.flag1", true);
+ flagValues.put("my.flag2", false);
+ flagValues.put("my.flag3", false);
+ flagValues.put("my.flag4", true);
+ ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues);
+
+ // The manifest has:
+ // <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+ // <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+ // <permission android:name="PERM3" android:featureFlag="my.flag3" />
+ // <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+ // <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+ // Therefore with the above flag values, only PERM1 and PERM2 should be present.
+
+ final ParsedPackage pkg = parser.parsePackage(testFile, 0, false);
+ List<String> permissionNames =
+ pkg.getPermissions().stream().map(ParsedComponent::getName).toList();
+ assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1");
+ assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4");
+ assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5");
+ } finally {
+ testFile.delete();
+ }
+ }
+
/**
* A subclass of package parser that adds a "cache_" prefix to the package name for the cached
* results. This is used by tests to tell if a ParsedPackage is generated from the cache or not.
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 1322545..b98af6b 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -37,6 +37,7 @@
import android.service.dreams.Flags;
import android.service.dreams.IDreamOverlayCallback;
import android.testing.TestableLooper;
+import android.view.KeyEvent;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -181,4 +182,15 @@
environment.advance(TestDreamEnvironment.DREAM_STATE_WOKEN);
verify(environment.getDreamOverlayClient()).onWakeRequested();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_DREAM_HANDLES_CONFIRM_KEYS)
+ public void testPartialKeyHandling() throws Exception {
+ TestDreamEnvironment environment = new TestDreamEnvironment.Builder(mTestableLooper)
+ .build();
+ environment.advance(TestDreamEnvironment.DREAM_STATE_STARTED);
+
+ // Ensure service does not crash from only receiving up event.
+ environment.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE));
+ }
}
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index ef85ba5..3d03bf2 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -46,6 +46,7 @@
import android.service.dreams.IDreamOverlayClient;
import android.service.dreams.IDreamService;
import android.testing.TestableLooper;
+import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowInsetsController;
@@ -390,6 +391,13 @@
}
}
+ /**
+ * Sends a key event to the dream.
+ */
+ public void dispatchKeyEvent(KeyEvent event) {
+ mService.dispatchKeyEvent(event);
+ }
+
private void wakeDream() throws RemoteException {
mService.wakeUp();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index c359412..cb15d6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3094,13 +3094,14 @@
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
any(), any(Handler.class), isNull(), bundleCaptor.capture());
+ Bundle bundle = bundleCaptor.getValue();
if (idleOptions != null) {
- assertEquals(idleOptions, bundleCaptor.getValue());
+ assertEquals(idleOptions, bundle);
} else {
- assertFalse("BAL flag needs to be false in alarm manager",
- bundleCaptor.getValue().getBoolean(
- ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
- true));
+ ActivityOptions options = ActivityOptions.fromBundle(bundle);
+ assertEquals("BAL should not be allowed in alarm manager",
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED,
+ options.getPendingIntentBackgroundActivityStartMode());
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index 8e1e339..c77ab0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -24,7 +24,6 @@
import android.content.ContentResolver;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -43,7 +42,6 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
/**
* Test SettingsToPropertiesMapper.
@@ -63,7 +61,6 @@
private HashMap<String, String> mSystemSettingsMap;
private HashMap<String, String> mGlobalSettingsMap;
- private HashMap<String, String> mConfigSettingsMap;
@Before
public void setUp() throws Exception {
@@ -74,11 +71,9 @@
.spyStatic(SystemProperties.class)
.spyStatic(Settings.Global.class)
.spyStatic(SettingsToPropertiesMapper.class)
- .spyStatic(Settings.Config.class)
.startMocking();
mSystemSettingsMap = new HashMap<>();
mGlobalSettingsMap = new HashMap<>();
- mConfigSettingsMap = new HashMap<>();
// Mock SystemProperties setter and various getters
doAnswer((Answer<Void>) invocationOnMock -> {
@@ -106,21 +101,6 @@
}
).when(() -> Settings.Global.getString(any(), anyString()));
- // Mock Settings.Config getstrings method
- doAnswer((Answer<Map<String, String>>) invocationOnMock -> {
- String namespace = invocationOnMock.getArgument(0);
- List<String> flags = invocationOnMock.getArgument(1);
- HashMap<String, String> values = new HashMap<>();
- for (String flag : flags) {
- String value = mConfigSettingsMap.get(namespace + "/" + flag);
- if (value != null) {
- values.put(flag, value);
- }
- }
- return values;
- }
- ).when(() -> Settings.Config.getStrings(anyString(), any()));
-
mTestMapper = new SettingsToPropertiesMapper(
mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {});
}
@@ -259,39 +239,4 @@
Assert.assertTrue(categories.contains("category2"));
Assert.assertTrue(categories.contains("category3"));
}
-
- @Test
- public void testGetStagedFlagsWithValueChange() {
- // mock up what is in the setting already
- mConfigSettingsMap.put("namespace_1/flag_1", "true");
- mConfigSettingsMap.put("namespace_1/flag_2", "true");
-
- // mock up input
- String namespace = "staged";
- Map<String, String> keyValueMap = new HashMap<>();
- // case 1: existing prop, stage the same value
- keyValueMap.put("namespace_1*flag_1", "true");
- // case 2: existing prop, stage a different value
- keyValueMap.put("namespace_1*flag_2", "false");
- // case 3: new prop
- keyValueMap.put("namespace_2*flag_1", "true");
- Properties props = new Properties(namespace, keyValueMap);
-
- HashMap<String, HashMap<String, String>> toStageProps =
- SettingsToPropertiesMapper.getStagedFlagsWithValueChange(props);
-
- HashMap<String, String> namespace_1_to_stage = toStageProps.get("namespace_1");
- HashMap<String, String> namespace_2_to_stage = toStageProps.get("namespace_2");
- Assert.assertTrue(namespace_1_to_stage != null);
- Assert.assertTrue(namespace_2_to_stage != null);
-
- String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
- String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
- String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
- Assert.assertTrue(namespace_1_flag_1 == null);
- Assert.assertTrue(namespace_1_flag_2 != null);
- Assert.assertTrue(namespace_2_flag_1 != null);
- Assert.assertTrue(namespace_1_flag_2.equals("false"));
- Assert.assertTrue(namespace_2_flag_1.equals("true"));
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 29f3720..1b0a8d2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -210,12 +210,10 @@
new Rect(0, 0, bitmapSize.x, bitmapSize.y),
new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
for (int mode: ALL_MODES) {
- for (boolean rtl: List.of(true, false)) {
- for (boolean parallax: List.of(true, false)) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, parallax, rtl, mode))
- .isEqualTo(crop);
- }
+ for (boolean parallax: List.of(true, false)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, parallax, mode))
+ .isEqualTo(crop);
}
}
}
@@ -235,11 +233,9 @@
int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
Point expectedCropSize = new Point(expectedWidth, 1000);
for (int mode: ALL_MODES) {
- for (boolean rtl: List.of(false, true)) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, true, rtl, mode))
- .isEqualTo(centerOf(crop, expectedCropSize));
- }
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, mode))
+ .isEqualTo(centerOf(crop, expectedCropSize));
}
}
@@ -258,11 +254,9 @@
Point bitmapSize = new Point(acceptableWidth, 1000);
Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
for (int mode : ALL_MODES) {
- for (boolean rtl : List.of(false, true)) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, true, rtl, mode))
- .isEqualTo(crop);
- }
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, mode))
+ .isEqualTo(crop);
}
}
}
@@ -292,11 +286,9 @@
for (int i = 0; i < crops.size(); i++) {
Rect crop = crops.get(i);
Rect expectedCrop = expectedAdjustedCrops.get(i);
- for (boolean rtl: List.of(false, true)) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
- .isEqualTo(expectedCrop);
- }
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, WallpaperCropper.ADD))
+ .isEqualTo(expectedCrop);
}
}
@@ -317,11 +309,9 @@
Point expectedCropSize = new Point(1000, 1000);
for (Rect crop: crops) {
- for (boolean rtl : List.of(false, true)) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
- .isEqualTo(centerOf(crop, expectedCropSize));
- }
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, WallpaperCropper.REMOVE))
+ .isEqualTo(centerOf(crop, expectedCropSize));
}
}
@@ -348,14 +338,14 @@
Rect crop = crops.get(i);
Rect expected = expectedAdjustedCrops.get(i);
assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+ crop, bitmapSize, displaySize, false, WallpaperCropper.BALANCE))
.isEqualTo(expected);
Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
Rect expectedTransposed = new Rect(
expected.top, expected.left, expected.bottom, expected.right);
assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
- transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+ transposedDisplaySize, false, WallpaperCropper.BALANCE))
.isEqualTo(expectedTransposed);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 9862663..1db97b9 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -186,7 +186,8 @@
new FileDescriptor(), /* screenshotFd= */ null,
BugreportParams.BUGREPORT_MODE_FULL,
/* flags= */ 0, new Listener(new CountDownLatch(1)),
- /* isScreenshotRequested= */ false);
+ /* isScreenshotRequested= */ false,
+ /* skipUserConsentUnused = */ false);
assertThat(mInjector.isBugreportStarted()).isTrue();
}
@@ -202,7 +203,8 @@
new FileDescriptor(), /* screenshotFd= */ null,
BugreportParams.BUGREPORT_MODE_FULL,
/* flags= */ 0, new Listener(new CountDownLatch(1)),
- /* isScreenshotRequested= */ false);
+ /* isScreenshotRequested= */ false,
+ /* skipUserConsentUnused = */ false);
assertThat(mInjector.isBugreportStarted()).isTrue();
}
@@ -216,7 +218,8 @@
new FileDescriptor(), /* screenshotFd= */ null,
BugreportParams.BUGREPORT_MODE_FULL,
/* flags= */ 0, new Listener(new CountDownLatch(1)),
- /* isScreenshotRequested= */ false));
+ /* isScreenshotRequested= */ false,
+ /* skipUserConsentUnused = */ false));
assertThat(thrown.getMessage()).contains("not an admin user");
}
@@ -232,7 +235,8 @@
new FileDescriptor(), /* screenshotFd= */ null,
BugreportParams.BUGREPORT_MODE_REMOTE,
/* flags= */ 0, new Listener(new CountDownLatch(1)),
- /* isScreenshotRequested= */ false));
+ /* isScreenshotRequested= */ false,
+ /* skipUserConsentUnused = */ false));
assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
}
@@ -243,7 +247,7 @@
Listener listener = new Listener(latch);
mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
mContext.getUserId(), new FileDescriptor(), mBugreportFile,
- /* keepOnRetrieval= */ false, listener);
+ /* keepOnRetrieval= */ false, /* skipUserConsent = */ false, listener);
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
assertThat(listener.getErrorCode()).isEqualTo(
BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index 131b380..3def48a 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -116,3 +116,20 @@
resource_dirs: ["res"],
manifest: "AndroidManifestApp7.xml",
}
+
+android_test_helper_app {
+ name: "PackageParserTestApp8",
+ sdk_version: "current",
+ srcs: ["**/*.java"],
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ resource_dirs: ["res"],
+ aaptflags: [
+ "--feature-flags my.flag1,my.flag2,my.flag3,my.flag4,unknown.flag",
+ ],
+ manifest: "AndroidManifestApp8.xml",
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
new file mode 100644
index 0000000..d489c1b
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.packageparserapp" >
+
+ <application>
+ <activity android:name=".TestActivity"
+ android:exported="true" />
+ </application>
+
+ <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+ <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+ <permission android:name="PERM3" android:featureFlag="my.flag3" />
+ <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+ <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+</manifest>
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 37e0818..5787780 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -24,6 +24,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -250,6 +252,7 @@
case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+ case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED:
case "android:activity.animationFinishedListener":
// KEY_ANIMATION_FINISHED_LISTENER
case "android:activity.animSpecs": // KEY_ANIM_SPECS
@@ -319,7 +322,7 @@
Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
+ "Please review if the given bundle should be protected with permissions.");
}
- assertTrue(unknownKeys.isEmpty());
+ assertThat(unknownKeys).isEmpty();
}
public static class TrampolineActivity extends Activity {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 5b1a18d..9b48cb9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -314,6 +314,18 @@
// Wallpaper is invisible because the lowest show-when-locked activity is opaque.
assertNull(wallpaperController.getWallpaperTarget());
+ // Only transient-launch transition will make notification shade as last resort target.
+ // This verifies that regular transition won't choose invisible keyguard as the target.
+ final WindowState keyguard = createWindow(null /* parent */,
+ WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE, "keyguard");
+ keyguard.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
+ registerTestTransitionPlayer();
+ final Transition transition = wallpaperWindow.mTransitionController.createTransition(
+ WindowManager.TRANSIT_CHANGE);
+ transition.collect(keyguard);
+ wallpaperController.adjustWallpaperWindows();
+ assertNull(wallpaperController.getWallpaperTarget());
+
// A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should
// be the one that is not show-when-locked.
final WindowState wallpaperWindow2 = createWallpaperWindow(mDisplayContent);
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index b8d1800..3f2b13a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -290,6 +290,16 @@
return Visibility.fromAccess(this.access)
}
+/** Return the [access] flags without the visibility */
+fun clearVisibility(access: Int): Int {
+ return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE).inv()
+}
+
+/** Return the visibility part of the [access] flags */
+fun getVisibility(access: Int): Int {
+ return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE)
+}
+
/*
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 6643492..c99ff0e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -195,6 +195,8 @@
return null
}
+ var newAccess = access
+
// Maybe rename the method.
val newName: String
val renameTo = filter.getRenameTo(currentClassName, name, descriptor)
@@ -205,8 +207,9 @@
// (the one with the @substitute/replace annotation).
// `name` is the name of the method we're currently visiting, so it's usually a
// "...$ravewnwood" name.
- if (!checkSubstitutionMethodCompatibility(
- classes, currentClassName, newName, name, descriptor, options.errors)) {
+ newAccess = checkSubstitutionMethodCompatibility(
+ classes, currentClassName, newName, name, descriptor, options.errors)
+ if (newAccess == NOT_COMPATIBLE) {
return null
}
@@ -221,7 +224,7 @@
// But note, we only use it when calling the super's method,
// but not for visitMethodInner(), because when subclass wants to change access,
// it can do so inside visitMethodInner().
- val newAccess = updateAccessFlags(access, name, descriptor)
+ newAccess = updateAccessFlags(newAccess, name, descriptor)
val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy,
renameTo != null,
@@ -303,4 +306,4 @@
return ret
}
}
-}
\ No newline at end of file
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
index 9d66c32..dc4f26bd 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
@@ -17,12 +17,19 @@
import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.clearVisibility
import com.android.hoststubgen.asm.getVisibility
import com.android.hoststubgen.asm.isStatic
+const val NOT_COMPATIBLE: Int = -1
+
/**
* Make sure substitution from and to methods have matching definition.
- * (static-ness, visibility.)
+ * (static-ness, etc)
+ *
+ * If the methods are compatible, return the "merged" [access] of the new method.
+ *
+ * If they are not compatible, returns [NOT_COMPATIBLE]
*/
fun checkSubstitutionMethodCompatibility(
classes: ClassNodes,
@@ -31,33 +38,31 @@
toMethodName: String, // the one with either a "_host" or "$ravenwood" prefix. (typically)
descriptor: String,
errors: HostStubGenErrors,
-): Boolean {
+): Int {
val from = classes.findMethod(className, fromMethodName, descriptor)
if (from == null) {
errors.onErrorFound(
- "Substitution-from method not found: $className.$fromMethodName$descriptor")
- return false
+ "Substitution-from method not found: $className.$fromMethodName$descriptor"
+ )
+ return NOT_COMPATIBLE
}
val to = classes.findMethod(className, toMethodName, descriptor)
if (to == null) {
// This shouldn't happen, because the visitor visited this method...
errors.onErrorFound(
- "Substitution-to method not found: $className.$toMethodName$descriptor")
- return false
+ "Substitution-to method not found: $className.$toMethodName$descriptor"
+ )
+ return NOT_COMPATIBLE
}
if (from.isStatic() != to.isStatic()) {
errors.onErrorFound(
"Substitution method must have matching static-ness: " +
- "$className.$fromMethodName$descriptor")
- return false
- }
- if (from.getVisibility().ordinal > to.getVisibility().ordinal) {
- errors.onErrorFound(
- "Substitution method cannot have smaller visibility than original: " +
- "$className.$fromMethodName$descriptor")
- return false
+ "$className.$fromMethodName$descriptor"
+ )
+ return NOT_COMPATIBLE
}
- return true
+ // Return the substitution's access flag but with the original method's visibility.
+ return clearVisibility (to.access) or getVisibility(from.access)
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 931f0c5..dd63892 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -644,9 +644,9 @@
suffix="_host"
)
- public static int nativeAddThree_host(int);
+ private static int nativeAddThree_host(int);
descriptor: (I)I
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
x: iload_0
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
index ab387e0..6d8a48a 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
@@ -73,7 +73,8 @@
@HostSideTestSubstitute(suffix = "_host")
public static native int nativeAddThree(int value);
- public static int nativeAddThree_host(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) {
return value + 3;
}
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
index 0ea90ed..75e2536 100644
--- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
@@ -71,7 +71,7 @@
addClass(cn)
}
- fun check(from: MethodNode?, to: MethodNode?, expected: Boolean) {
+ fun check(from: MethodNode?, to: MethodNode?, expected: Int) {
assertThat(checkSubstitutionMethodCompatibility(
classes,
cn.name,
@@ -82,21 +82,21 @@
)).isEqualTo(expected)
}
- check(staticPublic, staticPublic, true)
- check(staticPrivate, staticPrivate, true)
- check(nonStaticPublic, nonStaticPublic, true)
- check(nonStaticPProtected, nonStaticPProtected, true)
+ check(staticPublic, staticPublic, Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC)
+ check(staticPrivate, staticPrivate, Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC)
+ check(nonStaticPublic, nonStaticPublic, Opcodes.ACC_PUBLIC)
+ check(nonStaticPProtected, nonStaticPProtected, 0)
- check(staticPublic, null, false)
- check(null, staticPublic, false)
+ check(staticPublic, null, NOT_COMPATIBLE)
+ check(null, staticPublic, NOT_COMPATIBLE)
- check(staticPublic, nonStaticPublic, false)
- check(nonStaticPublic, staticPublic, false)
+ check(staticPublic, nonStaticPublic, NOT_COMPATIBLE)
+ check(nonStaticPublic, staticPublic, NOT_COMPATIBLE)
- check(staticPublic, staticPrivate, false)
- check(staticPrivate, staticPublic, true)
+ check(staticPublic, staticPrivate, Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC)
+ check(staticPrivate, staticPublic, Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC)
- check(nonStaticPublic, nonStaticPProtected, false)
- check(nonStaticPProtected, nonStaticPublic, true)
+ check(nonStaticPublic, nonStaticPProtected, Opcodes.ACC_PUBLIC)
+ check(nonStaticPProtected, nonStaticPublic, 0)
}
}
\ No newline at end of file