Merge "[ToA] Add metrics log in GrammaticalInflectionService" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 3f834fa..14658e5 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -82,7 +82,7 @@
"com.android.media.flags.bettertogether-aconfig-java",
"com.android.media.flags.editing-aconfig-java",
"com.android.media.flags.projection-aconfig-java",
- "com.android.net.thread.flags-aconfig-java",
+ "com.android.net.thread.platform.flags-aconfig-java",
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
@@ -786,8 +786,8 @@
// Thread network
aconfig_declarations {
- name: "com.android.net.thread.flags-aconfig",
- package: "com.android.net.thread.flags",
+ name: "com.android.net.thread.platform.flags-aconfig",
+ package: "com.android.net.thread.platform.flags",
srcs: ["core/java/android/net/thread/flags.aconfig"],
}
@@ -799,8 +799,8 @@
}
java_aconfig_library {
- name: "com.android.net.thread.flags-aconfig-java",
- aconfig_declarations: "com.android.net.thread.flags-aconfig",
+ name: "com.android.net.thread.platform.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.thread.platform.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -985,6 +985,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "android.tracing.flags_c_lib",
+ aconfig_declarations: "android.tracing.flags-aconfig",
+}
+
// App Widgets
aconfig_declarations {
name: "android.appwidget.flags-aconfig",
diff --git a/LSE_APP_COMPAT_OWNERS b/LSE_APP_COMPAT_OWNERS
new file mode 100644
index 0000000..3db0cd4
--- /dev/null
+++ b/LSE_APP_COMPAT_OWNERS
@@ -0,0 +1,6 @@
+# Owners for the App Compat flags (large_screen_experiences_app_compat)
+mcarli@google.com
+eevlachavas@google.com
+gracielawputri@google.com
+minagranic@google.com
+mariiasand@google.com
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 6337022..2df6d58 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -78,6 +78,73 @@
}
java_library {
+ name: "services.core-for-hoststubgen",
+ installable: false, // host only jar.
+ static_libs: [
+ "services.core",
+ ],
+ sdk_version: "core_platform",
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "services.core.ravenwood-base",
+ tools: ["hoststubgen"],
+ cmd: "$(location hoststubgen) " +
+ "@$(location ravenwood/ravenwood-standard-options.txt) " +
+
+ "--debug-log $(location hoststubgen_services.core.log) " +
+ "--stats-file $(location hoststubgen_services.core_stats.csv) " +
+
+ "--out-impl-jar $(location ravenwood.jar) " +
+
+ "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+
+ "--in-jar $(location :services.core-for-hoststubgen) " +
+ "--policy-override-file $(location ravenwood/services.core-ravenwood-policies.txt) " +
+ "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+ srcs: [
+ ":services.core-for-hoststubgen",
+ "ravenwood/services.core-ravenwood-policies.txt",
+ "ravenwood/ravenwood-standard-options.txt",
+ "ravenwood/ravenwood-annotation-allowed-classes.txt",
+ ],
+ out: [
+ "ravenwood.jar",
+
+ // Following files are created just as FYI.
+ "hoststubgen_keep_all.txt",
+ "hoststubgen_dump.txt",
+
+ "hoststubgen_services.core.log",
+ "hoststubgen_services.core_stats.csv",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "services.core.ravenwood",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":services.core.ravenwood-base{ravenwood.jar}",
+ ],
+ out: [
+ "services.core.ravenwood.jar",
+ ],
+}
+
+java_library {
+ name: "services.core.ravenwood-jarjar",
+ installable: false,
+ static_libs: [
+ "services.core.ravenwood",
+ ],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
+}
+
+java_library {
name: "mockito-ravenwood-prebuilt",
installable: false,
static_libs: [
@@ -121,6 +188,7 @@
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
+ "services.core.ravenwood-jarjar",
// Provide runtime versions of utils linked in below
"junit",
diff --git a/core/api/current.txt b/core/api/current.txt
index 4a2abf6..7dc15fb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7024,6 +7024,7 @@
method public void deleteNotificationChannelGroup(String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(String);
+ method @FlaggedApi("android.app.modes_api") public int getAutomaticZenRuleState(@NonNull String);
method public java.util.Map<java.lang.String,android.app.AutomaticZenRule> getAutomaticZenRules();
method public int getBubblePreference();
method @NonNull public android.app.NotificationManager.Policy getConsolidatedNotificationPolicy();
@@ -10088,7 +10089,7 @@
method public CharSequence coerceToText(android.content.Context);
method public String getHtmlText();
method public android.content.Intent getIntent();
- method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.app.PendingIntent getPendingIntent();
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.content.IntentSender getIntentSender();
method public CharSequence getText();
method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
method public android.net.Uri getUri();
@@ -10099,7 +10100,7 @@
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item build();
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setHtmlText(@Nullable String);
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntent(@Nullable android.content.Intent);
- method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntentSender(@Nullable android.content.IntentSender);
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setText(@Nullable CharSequence);
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setUri(@Nullable android.net.Uri);
}
@@ -11074,6 +11075,7 @@
method public boolean hasCategory(String);
method public boolean hasExtra(String);
method public boolean hasFileDescriptors();
+ method @FlaggedApi("android.security.enforce_intent_filter_match") public boolean isMismatchingFilter();
method public static android.content.Intent makeMainActivity(android.content.ComponentName);
method public static android.content.Intent makeMainSelectorActivity(String, String);
method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName);
@@ -53136,7 +53138,7 @@
field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
- field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 8192; // 0x2000
+ field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 8192; // 0x2000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a5e62ac..627b703 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2276,6 +2276,7 @@
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
@@ -12957,6 +12958,34 @@
}
+package android.service.ondeviceintelligence {
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
+ ctor public OnDeviceIntelligenceService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
+ method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
+ method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
+ ctor public OnDeviceTrustedInferenceService();
+ method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
+ method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ }
+
+}
+
package android.service.persistentdata {
@FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index ccfb17a..1923641 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -509,6 +509,12 @@
Methods must not throw generic exceptions (`java.lang.Throwable`)
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
+ Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+ Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+ Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.voice.HotwordDetectionService#getSystemService(String) parameter #0:
@@ -565,6 +571,8 @@
Missing nullability on parameter `args` in method `dump`
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
Missing nullability on parameter `base` in method `attachBaseContext`
+MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+ Missing nullability on method `openFileInput` return
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
Missing nullability on parameter `intent` in method `onUnbind`
MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4ec476e..e1e9d09 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1032,7 +1032,10 @@
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
+ method @NonNull public android.content.Intent addExtendedFlags(int);
+ method public int getExtendedFlags();
field public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED";
+ field public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1; // 0x1
}
public class SyncAdapterType implements android.os.Parcelable {
@@ -1703,6 +1706,7 @@
field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
+ field public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 64; // 0x40
}
}
@@ -1751,6 +1755,10 @@
field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
}
+ public class VirtualKeyboard implements java.io.Closeable {
+ method public int getInputDeviceId();
+ }
+
}
package android.hardware.lights {
@@ -3058,6 +3066,14 @@
}
+package android.service.chooser {
+
+ @FlaggedApi("android.service.chooser.enable_chooser_result") public final class ChooserResult implements android.os.Parcelable {
+ ctor public ChooserResult(int, @Nullable android.content.ComponentName, boolean);
+ }
+
+}
+
package android.service.dreams {
public abstract class DreamOverlayService extends android.app.Service {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 27808cb..658ddbf 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -2019,6 +2019,12 @@
New API must be flagged with @FlaggedApi: constructor android.content.AttributionSource(int,int,String,String,android.os.IBinder,String[],android.content.AttributionSource)
UnflaggedApi: android.content.AttributionSource#AttributionSource(int, int, String, String, android.os.IBinder, String[], int, android.content.AttributionSource):
New API must be flagged with @FlaggedApi: constructor android.content.AttributionSource(int,int,String,String,android.os.IBinder,String[],int,android.content.AttributionSource)
+UnflaggedApi: android.content.Intent#EXTENDED_FLAG_FILTER_MISMATCH:
+ New API must be flagged with @FlaggedApi: field android.content.Intent.EXTENDED_FLAG_FILTER_MISMATCH
+UnflaggedApi: android.content.Intent#addExtendedFlags(int):
+ New API must be flagged with @FlaggedApi: method android.content.Intent.addExtendedFlags(int)
+UnflaggedApi: android.content.Intent#getExtendedFlags():
+ New API must be flagged with @FlaggedApi: method android.content.Intent.getExtendedFlags()
UnflaggedApi: android.content.pm.UserInfo#isCommunalProfile():
New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isCommunalProfile()
UnflaggedApi: android.content.pm.UserInfo#isPrivateProfile():
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 926e297..ae556c9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -237,7 +237,6 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.MemInfoDumpProto;
-import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.AppSpecializationHooks;
@@ -1234,7 +1233,8 @@
}
@Override
- public final void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+ public final void scheduleTimeoutServiceForType(IBinder token, int startId,
+ @ServiceInfo.ForegroundServiceType int fgsType) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"scheduleTimeoutServiceForType. token=" + token);
@@ -3762,11 +3762,7 @@
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
activityToken, list);
- if (Flags.bundleClientTransactionFlag()) {
- clientTransaction.addTransactionItem(activityResultItem);
- } else {
- clientTransaction.addCallback(activityResultItem);
- }
+ clientTransaction.addTransactionItem(activityResultItem);
try {
mAppThread.scheduleTransaction(clientTransaction);
} catch (RemoteException e) {
@@ -4553,11 +4549,7 @@
final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
/* dontReport */ false, /* autoEnteringPip */ false);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(pauseActivityItem);
- } else {
- transaction.setLifecycleStateRequest(pauseActivityItem);
- }
+ transaction.addTransactionItem(pauseActivityItem);
executeTransaction(transaction);
}
@@ -4565,11 +4557,7 @@
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(resumeActivityItem);
- } else {
- transaction.setLifecycleStateRequest(resumeActivityItem);
- }
+ transaction.addTransactionItem(resumeActivityItem);
executeTransaction(transaction);
}
@@ -5164,7 +5152,8 @@
}
}
- private void handleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+ private void handleTimeoutServiceForType(IBinder token, int startId,
+ @ServiceInfo.ForegroundServiceType int fgsType) {
Service s = mServices.get(token);
if (s != null) {
try {
@@ -6189,13 +6178,8 @@
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
// Schedule the transaction.
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(activityRelaunchItem);
- transaction.addTransactionItem(lifecycleRequest);
- } else {
- transaction.addCallback(activityRelaunchItem);
- transaction.setLifecycleStateRequest(lifecycleRequest);
- }
+ transaction.addTransactionItem(activityRelaunchItem);
+ transaction.addTransactionItem(lifecycleRequest);
executeTransaction(transaction);
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b5e3556..8f81ae2 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -225,6 +225,7 @@
boolean removeAutomaticZenRule(String id, boolean fromUser);
boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
+ int getAutomaticZenRuleState(String id);
void setAutomaticZenRuleState(String id, in Condition condition);
byte[] getBackupPayload(int user);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index be7199b..db216b1 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -145,7 +145,7 @@
* reflection, but it will serve as noticeable discouragement from
* doing such a thing.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @android.ravenwood.annotation.RavenwoodKeep
private void checkInstrumenting(String method) {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -155,16 +155,12 @@
}
}
- private void checkInstrumenting$ravenwood(String method) {
- // At the moment, Ravenwood doesn't attach a Context, but we're only ever
- // running code as part of tests, so we continue quietly
- }
-
/**
* Returns if it is being called in an instrumentation environment.
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isInstrumenting() {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -328,6 +324,7 @@
*
* @see #getTargetContext
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public Context getContext() {
return mInstrContext;
}
@@ -352,6 +349,7 @@
*
* @see #getContext
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public Context getTargetContext() {
return mAppContext;
}
@@ -2402,6 +2400,17 @@
mThread = thread;
}
+ /**
+ * Only sets the Context up, keeps everything else null.
+ *
+ * @hide
+ */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public final void basicInit(Context context) {
+ mInstrContext = context;
+ mAppContext = context;
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static void checkStartActivityResult(int res, Object intent) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d6e8ae3..26f85f7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -80,6 +80,7 @@
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -3023,37 +3024,44 @@
* @hide
*/
public String loadHeaderAppName(Context context) {
- CharSequence name = null;
- // Check if there is a non-empty substitute app name and return that.
- if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
- name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
- if (!TextUtils.isEmpty(name)) {
- return name.toString();
- }
- }
- // If not, try getting the app info from extras.
- if (context == null) {
- return null;
- }
- final PackageManager pm = context.getPackageManager();
- if (TextUtils.isEmpty(name)) {
- if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
- final ApplicationInfo info = extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo.class);
- if (info != null) {
- name = pm.getApplicationLabel(info);
+ Trace.beginSection("Notification#loadHeaderAppName");
+
+ try {
+ CharSequence name = null;
+ // Check if there is a non-empty substitute app name and return that.
+ if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
+ name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
+ if (!TextUtils.isEmpty(name)) {
+ return name.toString();
}
}
+ // If not, try getting the app info from extras.
+ if (context == null) {
+ return null;
+ }
+ final PackageManager pm = context.getPackageManager();
+ if (TextUtils.isEmpty(name)) {
+ if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+ final ApplicationInfo info = extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ if (info != null) {
+ name = pm.getApplicationLabel(info);
+ }
+ }
+ }
+ // If that's still empty, use the one from the context directly.
+ if (TextUtils.isEmpty(name)) {
+ name = pm.getApplicationLabel(context.getApplicationInfo());
+ }
+ // If there's still nothing, ¯\_(ツ)_/¯
+ if (TextUtils.isEmpty(name)) {
+ return null;
+ }
+ return name.toString();
+ } finally {
+ Trace.endSection();
}
- // If that's still empty, use the one from the context directly.
- if (TextUtils.isEmpty(name)) {
- name = pm.getApplicationLabel(context.getApplicationInfo());
- }
- // If there's still nothing, ¯\_(ツ)_/¯
- if (TextUtils.isEmpty(name)) {
- return null;
- }
- return name.toString();
}
/**
@@ -6722,23 +6730,29 @@
*/
@NonNull
public static Notification.Builder recoverBuilder(Context context, Notification n) {
- // Re-create notification context so we can access app resources.
- ApplicationInfo applicationInfo = n.extras.getParcelable(
- EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
- Context builderContext;
- if (applicationInfo != null) {
- try {
- builderContext = context.createApplicationContext(applicationInfo,
- Context.CONTEXT_RESTRICTED);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
- builderContext = context; // try with our context
- }
- } else {
- builderContext = context; // try with given context
- }
+ Trace.beginSection("Notification.Builder#recoverBuilder");
- return new Builder(builderContext, n);
+ try {
+ // Re-create notification context so we can access app resources.
+ ApplicationInfo applicationInfo = n.extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
+ Context builderContext;
+ if (applicationInfo != null) {
+ try {
+ builderContext = context.createApplicationContext(applicationInfo,
+ Context.CONTEXT_RESTRICTED);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
+ builderContext = context; // try with our context
+ }
+ } else {
+ builderContext = context; // try with given context
+ }
+
+ return new Builder(builderContext, n);
+ } finally {
+ Trace.endSection();
+ }
}
/**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 9dfb5b0..d49a254 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1406,6 +1406,26 @@
}
/**
+ * Returns the current activation state of an {@link AutomaticZenRule}.
+ *
+ * <p>Returns {@link Condition#STATE_UNKNOWN} if the rule does not exist or the calling
+ * package doesn't have access to it.
+ *
+ * @param id The id of the rule
+ * @return the state of the rule.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @Condition.State
+ public int getAutomaticZenRuleState(@NonNull String id) {
+ INotificationManager service = getService();
+ try {
+ return service.getAutomaticZenRuleState(id);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Informs the notification manager that the state of an {@link AutomaticZenRule} has changed.
* Use this method to put the system into Do Not Disturb mode or request that it exits Do Not
* Disturb mode. The calling app must own the provided {@link android.app.AutomaticZenRule}.
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index d470299..fe8655c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1164,7 +1164,7 @@
}
/** @hide */
- public final void callOnTimeLimitExceeded(int startId, int fgsType) {
+ public final void callOnTimeLimitExceeded(int startId, @ForegroundServiceType int fgsType) {
// Note, because all the service callbacks (and other similar callbacks, e.g. activity
// callbacks) are delivered using the main handler, it's possible the service is already
// stopped when before this method is called, so we do a double check here.
@@ -1189,10 +1189,11 @@
* Callback called when a particular foreground service type has timed out.
*
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
- * the service started.
- * @param fgsType the foreground service type which caused the timeout.
+ * the service started.
+ * @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
+ * caused the timeout.
*/
@FlaggedApi(Flags.FLAG_INTRODUCE_NEW_SERVICE_ONTIMEOUT_CALLBACK)
- public void onTimeout(int startId, int fgsType) {
+ public void onTimeout(int startId, @ForegroundServiceType int fgsType) {
}
}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a045eae..7903f1c 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,7 +16,7 @@
package android.app;
-import static android.app.Flags.enableNightModeCache;
+import static android.app.Flags.enableNightModeBinderCache;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -916,7 +916,7 @@
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+ @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_BINDER_CACHE)
public static void invalidateNightModeCache() {
IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
NIGHT_MODE_API);
@@ -938,7 +938,7 @@
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (enableNightModeCache()) {
+ if (enableNightModeBinderCache()) {
return mNightModeCache.query(null);
} else {
return getNightModeFromServer();
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 7d5d5c1..986205a 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,10 +16,11 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -185,7 +186,7 @@
* <p>This mode only allows a single secondary user on the device blocking the creation of
* additional secondary users.
*/
- @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index 3c56aaf..eeaf0b3 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,13 +16,13 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED;
import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
import android.os.UserManager;
import java.util.Objects;
@@ -188,13 +188,13 @@
/**
* String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
*/
- @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
+ @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
/**
* String identifier for {@link DevicePolicyManager#setRequiredPasswordComplexity}.
*/
- @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
+ @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a6fda9d..083705b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,8 +53,11 @@
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -90,7 +93,6 @@
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -153,10 +155,10 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
-import com.android.internal.os.Zygote;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@@ -2879,7 +2881,7 @@
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
/**
@@ -13447,7 +13449,7 @@
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional = true)
@SuppressLint("RequiresPermission")
- @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
+ @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
public @Nullable SystemUpdateInfo getPendingSystemUpdate(@Nullable ComponentName admin) {
throwIfParentInstance("getPendingSystemUpdate");
try {
@@ -16608,7 +16610,7 @@
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true)
@SuppressLint("RequiresPermission")
- @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
+ @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
@NonNull public String getEnrollmentSpecificId() {
throwIfParentInstance("getEnrollmentSpecificId");
if (mService == null) {
@@ -17134,7 +17136,7 @@
*/
@SystemApi
@RequiresPermission(value = MANAGE_DEVICE_POLICY_THEFT_DETECTION)
- @FlaggedApi(Flags.FLAG_DEVICE_THEFT_API_ENABLED)
+ @FlaggedApi(FLAG_DEVICE_THEFT_API_ENABLED)
public boolean isTheftDetectionTriggered() {
throwIfParentInstance("isTheftDetectionTriggered");
if (mService == null) {
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index ed1b8ca..477f2e0 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -16,6 +16,8 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED;
+
import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -24,7 +26,6 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Build;
@@ -610,7 +611,7 @@
* <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled)
* @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean)
*/
- @FlaggedApi(Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
+ @FlaggedApi(FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
public static final int TAG_BACKUP_SERVICE_TOGGLED =
SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
/**
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index d34ab60..4d8e0d5 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -60,7 +60,7 @@
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public class OnDeviceIntelligenceManager {
- private static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
private final Context mContext;
private final IOnDeviceIntelligenceManager mService;
@@ -86,6 +86,10 @@
// version and package name of the remote service implementing this.
try {
RemoteCallback callback = new RemoteCallback(result -> {
+ if (result == null) {
+ Binder.withCleanCallingIdentity(
+ () -> callbackExecutor.execute(() -> versionConsumer.accept(0)));
+ }
long version = result.getLong(API_VERSION_BUNDLE_KEY);
Binder.withCleanCallingIdentity(
() -> callbackExecutor.execute(() -> versionConsumer.accept(version)));
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 612d433..79696e0 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -29,6 +29,7 @@
import android.os.RemoteException;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,12 +86,15 @@
* @param item A single message that can contain a client activity/window request/callback.
*/
public void addTransactionItem(@NonNull ClientTransactionItem item) {
- if (mTransactionItems == null) {
- mTransactionItems = new ArrayList<>();
+ if (Flags.bundleClientTransactionFlag()) {
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
+ }
+ mTransactionItems.add(item);
}
- mTransactionItems.add(item);
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
+ // Populate even if mTransactionItems is set to support the UnsupportedAppUsage.
if (item.isActivityLifecycleItem()) {
setLifecycleStateRequest((ActivityLifecycleItem) item);
} else {
@@ -114,7 +118,7 @@
*/
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@Deprecated
- public void addCallback(@NonNull ClientTransactionItem activityCallback) {
+ private void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
}
@@ -169,7 +173,7 @@
*/
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@Deprecated
- public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
+ private void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
if (mLifecycleStateRequest != null) {
return;
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 9f97f6f..1a8136e 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -23,7 +23,6 @@
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
-import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@
* window configuration.
*/
public void onDisplayChanged(int displayId) {
- if (!isBundleClientTransactionFlagEnabled()) {
+ if (!bundleClientTransactionFlag()) {
return;
}
if (ActivityThread.isSystem()) {
@@ -76,10 +75,4 @@
}
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
-
- /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
- public boolean isBundleClientTransactionFlagEnabled() {
- // Can't read flag from isolated process.
- return !Process.isIsolated() && bundleClientTransactionFlag();
- }
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 406e00a..fa73c99 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -40,7 +40,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.IBinder;
-import android.os.Process;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -218,8 +217,6 @@
final boolean shouldTrackConfigUpdatedContext =
// No configuration change for local transaction.
!mTransactionHandler.isExecutingLocalTransaction()
- // Can't read flag from isolated process.
- && !Process.isIsolated()
&& bundleClientTransactionFlag();
final Context configUpdatedContext = shouldTrackConfigUpdatedContext
? item.getContextToUpdate(mTransactionHandler)
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 1ae5264..27a38cc 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -1,8 +1,11 @@
package: "android.app"
flag {
- namespace: "system_performance"
- name: "enable_night_mode_cache"
+ namespace: "systemui"
+ name: "enable_night_mode_binder_cache"
description: "Enables the use of binder caching for system night mode."
bug: "255999432"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index ab8db6e..24d6a5c 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -29,3 +29,18 @@
bug: "291737188"
is_fixed_read_only: true
}
+
+flag {
+ namespace: "virtual_devices"
+ name: "metrics_collection"
+ description: "Enable collection of VDM-related metrics"
+ bug: "324842215"
+ is_fixed_read_only: true
+}
+
+flag {
+ namespace: "virtual_devices"
+ name: "camera_device_awareness"
+ description: "Enable device awareness in camera service"
+ bug: "305170199"
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index eb357fe..728c350 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -26,8 +26,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
@@ -213,7 +211,7 @@
final CharSequence mText;
final String mHtmlText;
final Intent mIntent;
- final PendingIntent mPendingIntent;
+ final IntentSender mIntentSender;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Uri mUri;
private TextLinks mTextLinks;
@@ -225,12 +223,11 @@
* A builder for a ClipData Item.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
- @SuppressLint("PackageLayering")
public static final class Builder {
private CharSequence mText;
private String mHtmlText;
private Intent mIntent;
- private PendingIntent mPendingIntent;
+ private IntentSender mIntentSender;
private Uri mUri;
/**
@@ -264,18 +261,20 @@
}
/**
- * Sets the PendingIntent for the item to be constructed. To prevent receiving apps from
- * improperly manipulating the intent to launch another activity as this caller, the
- * provided PendingIntent must be immutable (see {@link PendingIntent#FLAG_IMMUTABLE}).
- * The system will clean up the PendingIntent when it is no longer used.
+ * Sets the {@link IntentSender} for the item to be constructed. To prevent receiving
+ * apps from improperly manipulating the intent to launch another activity as this
+ * caller, the provided IntentSender must be immutable.
+ *
+ * If there is a fixed lifetime for this ClipData (ie. for drag and drop), the system
+ * will cancel the IntentSender when it is no longer used.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
@NonNull
- public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
- if (pendingIntent != null && !pendingIntent.isImmutable()) {
- throw new IllegalArgumentException("Expected pending intent to be immutable");
+ public Builder setIntentSender(@Nullable IntentSender intentSender) {
+ if (intentSender != null && !intentSender.isImmutable()) {
+ throw new IllegalArgumentException("Expected intent sender to be immutable");
}
- mPendingIntent = pendingIntent;
+ mIntentSender = intentSender;
return this;
}
@@ -295,7 +294,7 @@
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
@NonNull
public Item build() {
- return new Item(mText, mHtmlText, mIntent, mPendingIntent, mUri);
+ return new Item(mText, mHtmlText, mIntent, mIntentSender, mUri);
}
}
@@ -305,7 +304,7 @@
mText = other.mText;
mHtmlText = other.mHtmlText;
mIntent = other.mIntent;
- mPendingIntent = other.mPendingIntent;
+ mIntentSender = other.mIntentSender;
mUri = other.mUri;
mActivityInfo = other.mActivityInfo;
mTextLinks = other.mTextLinks;
@@ -366,7 +365,7 @@
/**
* Builder ctor.
*/
- private Item(CharSequence text, String htmlText, Intent intent, PendingIntent pendingIntent,
+ private Item(CharSequence text, String htmlText, Intent intent, IntentSender intentSender,
Uri uri) {
if (htmlText != null && text == null) {
throw new IllegalArgumentException(
@@ -375,7 +374,7 @@
mText = text;
mHtmlText = htmlText;
mIntent = intent;
- mPendingIntent = pendingIntent;
+ mIntentSender = intentSender;
mUri = uri;
}
@@ -401,12 +400,12 @@
}
/**
- * Returns the pending intent in this Item.
+ * Returns the {@link IntentSender} in this Item.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
@Nullable
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
+ public IntentSender getIntentSender() {
+ return mIntentSender;
}
/**
@@ -1131,35 +1130,6 @@
}
/**
- * Checks if this clip data has a pending intent that is an activity type.
- * @hide
- */
- public boolean hasActivityPendingIntents() {
- final int size = mItems.size();
- for (int i = 0; i < size; i++) {
- final Item item = mItems.get(i);
- if (item.mPendingIntent != null && item.mPendingIntent.isActivity()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Cleans up all pending intents in the ClipData.
- * @hide
- */
- public void cleanUpPendingIntents() {
- final int size = mItems.size();
- for (int i = 0; i < size; i++) {
- final Item item = mItems.get(i);
- if (item.mPendingIntent != null) {
- item.mPendingIntent.cancel();
- }
- }
- }
-
- /**
* Prepare this {@link ClipData} to leave an app process.
*
* @hide
@@ -1361,7 +1331,7 @@
TextUtils.writeToParcel(item.mText, dest, flags);
dest.writeString8(item.mHtmlText);
dest.writeTypedObject(item.mIntent, flags);
- dest.writeTypedObject(item.mPendingIntent, flags);
+ dest.writeTypedObject(item.mIntentSender, flags);
dest.writeTypedObject(item.mUri, flags);
dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags);
dest.writeTypedObject(item.mTextLinks, flags);
@@ -1381,11 +1351,11 @@
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
String htmlText = in.readString8();
Intent intent = in.readTypedObject(Intent.CREATOR);
- PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ IntentSender intentSender = in.readTypedObject(IntentSender.CREATOR);
Uri uri = in.readTypedObject(Uri.CREATOR);
ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
- Item item = new Item(text, htmlText, intent, pendingIntent, uri);
+ Item item = new Item(text, htmlText, intent, intentSender, uri);
item.setActivityInfo(info);
item.setTextLinks(textLinks);
mItems.add(item);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index e6e7fa8..13c3ede 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4815,7 +4815,9 @@
* @see android.net.thread.ThreadNetworkManager
* @hide
*/
- @FlaggedApi(com.android.net.thread.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
+ // TODO (b/325886480): update the flag to
+ // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
@SystemApi
public static final String THREAD_NETWORK_SERVICE = "thread_network";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0bcbb8e..fd2af99 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7656,6 +7656,22 @@
/** @hide */
public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
+ EXTENDED_FLAG_FILTER_MISMATCH,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExtendedFlags {}
+
+ /**
+ * This flag is not normally set by application code, but set for you by the system if
+ * an external intent does not match the receiving component's intent filter.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0;
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// toUri() and parseUri() options.
@@ -7775,6 +7791,7 @@
private int mFlags;
/** Set of in-process flags which are never parceled */
private int mLocalFlags;
+ private int mExtendedFlags;
private ArraySet<String> mCategories;
@UnsupportedAppUsage
private Bundle mExtras;
@@ -7834,6 +7851,7 @@
if (copyMode != COPY_MODE_FILTER) {
this.mFlags = o.mFlags;
+ this.mExtendedFlags = o.mExtendedFlags;
this.mContentUserHint = o.mContentUserHint;
this.mLaunchToken = o.mLaunchToken;
if (o.mSourceBounds != null) {
@@ -8161,12 +8179,17 @@
// launch flags
else if (uri.startsWith("launchFlags=", i)) {
- intent.mFlags = Integer.decode(value).intValue();
+ intent.mFlags = Integer.decode(value);
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
}
+ // extended flags
+ else if (uri.startsWith("extendedLaunchFlags=", i)) {
+ intent.mExtendedFlags = Integer.decode(value);
+ }
+
// package
else if (uri.startsWith("package=", i)) {
intent.mPackage = value;
@@ -8374,7 +8397,7 @@
isIntentFragment = true;
i += 12;
int j = uri.indexOf(')', i);
- intent.mFlags = Integer.decode(uri.substring(i, j)).intValue();
+ intent.mFlags = Integer.decode(uri.substring(i, j));
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
@@ -9868,6 +9891,22 @@
return mFlags;
}
+ /**
+ * Retrieve any extended flags associated with this intent. You will
+ * normally just set them with {@link #setExtendedFlags} and let the system
+ * take the appropriate action with them.
+ *
+ * @return The currently set extended flags.
+ * @see #addExtendedFlags
+ * @see #removeExtendedFlags
+ *
+ * @hide
+ */
+ @TestApi
+ public @ExtendedFlags int getExtendedFlags() {
+ return mExtendedFlags;
+ }
+
/** @hide */
@UnsupportedAppUsage
public boolean isExcludingStopped() {
@@ -11198,6 +11237,23 @@
}
/**
+ * Add additional extended flags to the intent (or with existing flags value).
+ *
+ * @param flags The new flags to set.
+ * @return Returns the same Intent object, for chaining multiple calls into
+ * a single statement.
+ * @see #getExtendedFlags
+ * @see #removeExtendedFlags
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull Intent addExtendedFlags(@ExtendedFlags int flags) {
+ mExtendedFlags |= flags;
+ return this;
+ }
+
+ /**
* Remove these flags from the intent.
*
* @param flags The flags to remove.
@@ -11210,6 +11266,19 @@
}
/**
+ * Remove these extended flags from the intent.
+ *
+ * @param flags The flags to remove.
+ * @see #getExtendedFlags
+ * @see #addExtendedFlags
+ *
+ * @hide
+ */
+ public void removeExtendedFlags(@ExtendedFlags int flags) {
+ mExtendedFlags &= ~flags;
+ }
+
+ /**
* (Usually optional) Set an explicit application package name that limits
* the components this Intent will resolve to. If left to the default
* value of null, all components in all applications will considered.
@@ -11513,6 +11582,7 @@
changes |= FILL_IN_COMPONENT;
}
mFlags |= other.mFlags;
+ mExtendedFlags |= other.mExtendedFlags;
if (other.mSourceBounds != null
&& (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
mSourceBounds = new Rect(other.mSourceBounds);
@@ -11748,6 +11818,13 @@
first = false;
b.append("flg=0x").append(Integer.toHexString(mFlags));
}
+ if (mExtendedFlags != 0) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("xflg=0x").append(Integer.toHexString(mExtendedFlags));
+ }
if (mPackage != null) {
if (!first) {
b.append(' ');
@@ -11846,6 +11923,9 @@
if (mFlags != 0) {
proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
}
+ if (mExtendedFlags != 0) {
+ proto.write(IntentProto.EXTENDED_FLAG, "0x" + Integer.toHexString(mExtendedFlags));
+ }
if (mPackage != null) {
proto.write(IntentProto.PACKAGE, mPackage);
}
@@ -12019,6 +12099,10 @@
if (mFlags != 0) {
uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
}
+ if (mExtendedFlags != 0) {
+ uri.append("extendedLaunchFlags=0x").append(Integer.toHexString(mExtendedFlags))
+ .append(';');
+ }
if (mPackage != null && !mPackage.equals(defPackage)) {
uri.append("package=").append(Uri.encode(mPackage)).append(';');
}
@@ -12068,6 +12152,7 @@
out.writeString8(mType);
out.writeString8(mIdentifier);
out.writeInt(mFlags);
+ out.writeInt(mExtendedFlags);
out.writeString8(mPackage);
ComponentName.writeToParcel(mComponent, out);
@@ -12136,6 +12221,7 @@
mType = in.readString8();
mIdentifier = in.readString8();
mFlags = in.readInt();
+ mExtendedFlags = in.readInt();
mPackage = in.readString8();
mComponent = ComponentName.readFromParcel(in);
@@ -12560,6 +12646,32 @@
}
/**
+ * Whether the intent mismatches all intent filters declared in the receiving component.
+ * <p>
+ * When a component receives an intent, normally obtainable through the following methods:
+ * <ul>
+ * <li> {@link BroadcastReceiver#onReceive(Context, Intent)}
+ * <li> {@link Activity#getIntent()}
+ * <li> {@link Activity#onNewIntent)}
+ * <li> {@link android.app.Service#onStartCommand(Intent, int, int)}
+ * <li> {@link android.app.Service#onBind(Intent)}
+ * </ul>
+ * The developer can call this method to check if this intent does not match any of its
+ * declared intent filters. A non-matching intent can be delivered when the intent sender
+ * explicitly set the component through {@link #setComponent} or {@link #setClassName}.
+ * <p>
+ * This method always returns {@code false} if the intent originated from within the same
+ * application or the system, because these cases are always exempted from security checks.
+ *
+ * @return Returns true if the intent does not match any intent filters declared in the
+ * receiving component.
+ */
+ @FlaggedApi(android.security.Flags.FLAG_ENFORCE_INTENT_FILTER_MATCH)
+ public boolean isMismatchingFilter() {
+ return (mExtendedFlags & EXTENDED_FLAG_FILTER_MISMATCH) != 0;
+ }
+
+ /**
* @hide
*/
@android.ravenwood.annotation.RavenwoodThrow
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 62db65f..cec49c7 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -130,4 +130,6 @@
void unRegisterDumpCallback(IDumpCallback cb);
void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
+
+ List<UserHandle> getUserProfiles();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 9c859c4..e437925 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -695,12 +695,23 @@
* Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
*/
public List<UserHandle> getProfiles() {
- if (mUserManager.isManagedProfile()) {
- // If it's a managed profile, only return the current profile.
- final List result = new ArrayList(1);
+ if (mUserManager.isManagedProfile()
+ || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
+ && android.os.Flags.allowPrivateProfile()
+ && mUserManager.isPrivateProfile())) {
+ // If it's a managed or private profile, only return the current profile.
+ final List result = new ArrayList(1);
result.add(android.os.Process.myUserHandle());
return result;
} else {
+ if (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()) {
+ try {
+ return mService.getUserProfiles();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
return mUserManager.getUserProfiles();
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2a974ed..7a015cd 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3974,7 +3974,9 @@
* The device is capable of communicating with other devices via
* <a href="https://www.threadgroup.org">Thread</a> networking protocol.
*/
- @FlaggedApi(com.android.net.thread.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
+ // TODO (b/325886480): update the flag to
+ // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
index 26e5129..e2ce723 100644
--- a/core/java/android/hardware/SerialManager.java
+++ b/core/java/android/hardware/SerialManager.java
@@ -29,6 +29,7 @@
* @hide
*/
@SystemService(Context.SERIAL_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialManager {
private static final String TAG = "SerialManager";
@@ -69,6 +70,8 @@
* @return the serial port
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = ParcelFileDescriptor.class, reason =
+ "Needs socketpair() to offer accurate emulation")
public SerialPort openSerialPort(String name, int speed) throws IOException {
try {
ParcelFileDescriptor pfd = mService.openSerialPort(name);
diff --git a/core/java/android/hardware/SerialManagerInternal.java b/core/java/android/hardware/SerialManagerInternal.java
new file mode 100644
index 0000000..9132da0
--- /dev/null
+++ b/core/java/android/hardware/SerialManagerInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+
+import java.util.function.Supplier;
+
+/**
+ * Internal interactions with {@link SerialManager}.
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public abstract class SerialManagerInternal {
+ public abstract void addVirtualSerialPortForTest(@NonNull String name,
+ @NonNull Supplier<ParcelFileDescriptor> supplier);
+
+ public abstract void removeVirtualSerialPortForTest(@NonNull String name);
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 2add77e..57b437f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -558,8 +558,11 @@
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
- * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
+ * configuration. If {@link #INFO_SESSION_CONFIGURATION_QUERY_VERSION} is
+ * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, then this list will only contain
+ * CONTROL_ZOOM_RATIO_RANGE and SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ *
+ * @see INFO_SESSION_CONFIGURATION_QUERY_VERSION
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 1a0074f..991bade 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1718,7 +1718,7 @@
* <p>Other than that, the characteristics returned here can be used in the same way as
* those returned from {@link CameraManager#getCameraCharacteristics}.</p>
*
- * @param sessionConfig : The session configuration for which characteristics are fetched.
+ * @param sessionConfig The session configuration for which characteristics are fetched.
* @return CameraCharacteristics specific to a given session configuration.
*
* @throws IllegalArgumentException if the session configuration is invalid
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 53a9a75..c091062 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -80,6 +80,11 @@
*/
public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2;
+ /**
+ * Maximum brightness is restricted due to the Wear bedtime mode.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE = 3;
+
/** Brightness */
public final float brightness;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8f0e0c9..eb26a76 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -367,6 +368,8 @@
* @see #createVirtualDisplay
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6;
/**
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index 1cc910c..e47a48d 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -48,14 +48,13 @@
private static final int GRAVITY_RIGHT = 0x2;
private static final int GRAVITY_TOP = 0x4;
private static final int GRAVITY_BOTTOM = 0x8;
- private static final int GRAVITY_CENTER =
- GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_TOP | GRAVITY_BOTTOM;
- private static final int GRAVITY_CENTER_HORIZONTAL = GRAVITY_LEFT | GRAVITY_RIGHT;
+ private static final int TEXT_PADDING_IN_DP = 1;
private static final int KEY_PADDING_IN_DP = 3;
private static final int KEYBOARD_PADDING_IN_DP = 10;
private static final int KEY_RADIUS_IN_DP = 5;
private static final int KEYBOARD_RADIUS_IN_DP = 10;
- private static final int GLYPH_TEXT_SIZE_IN_SP = 10;
+ private static final int MIN_GLYPH_TEXT_SIZE_IN_SP = 10;
+ private static final int MAX_GLYPH_TEXT_SIZE_IN_SP = 20;
private final List<KeyDrawable> mKeyDrawables = new ArrayList<>();
@@ -107,6 +106,8 @@
}
int rowCount = keys.length;
float keyHeight = (float) (height - rowCount * 2 * keyPadding) / rowCount;
+ // Based on key height calculate the max text size that can fit for typing keys
+ mResourceProvider.calculateBestTextSizeForKey(keyHeight);
float isoEnterKeyLeft = 0;
float isoEnterKeyTop = 0;
float isoEnterWidthUnit = 0;
@@ -136,16 +137,19 @@
}
if (PhysicalKeyLayout.isSpecialKey(row[j])) {
mKeyDrawables.add(new TypingKey(null, keyRect, keyRadius,
+ mResourceProvider.getTextPadding(),
mResourceProvider.getSpecialKeyPaint(),
mResourceProvider.getSpecialKeyPaint(),
mResourceProvider.getSpecialKeyPaint()));
} else if (PhysicalKeyLayout.isKeyPositionUnsure(row[j])) {
mKeyDrawables.add(new UnsureTypingKey(row[j].glyph(), keyRect,
- keyRadius, mResourceProvider.getTypingKeyPaint(),
+ keyRadius, mResourceProvider.getTextPadding(),
+ mResourceProvider.getTypingKeyPaint(),
mResourceProvider.getPrimaryGlyphPaint(),
mResourceProvider.getSecondaryGlyphPaint()));
} else {
mKeyDrawables.add(new TypingKey(row[j].glyph(), keyRect, keyRadius,
+ mResourceProvider.getTextPadding(),
mResourceProvider.getTypingKeyPaint(),
mResourceProvider.getPrimaryGlyphPaint(),
mResourceProvider.getSecondaryGlyphPaint()));
@@ -192,15 +196,18 @@
private final RectF mKeyRect;
private final float mKeyRadius;
+ private final float mTextPadding;
private final Paint mKeyPaint;
private final Paint mBaseTextPaint;
private final Paint mModifierTextPaint;
private final List<GlyphDrawable> mGlyphDrawables = new ArrayList<>();
private TypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, RectF keyRect,
- float keyRadius, Paint keyPaint, Paint baseTextPaint, Paint modifierTextPaint) {
+ float keyRadius, float textPadding, Paint keyPaint, Paint baseTextPaint,
+ Paint modifierTextPaint) {
mKeyRect = keyRect;
mKeyRadius = keyRadius;
+ mTextPadding = textPadding;
mKeyPaint = keyPaint;
mBaseTextPaint = baseTextPaint;
mModifierTextPaint = modifierTextPaint;
@@ -219,20 +226,17 @@
if (!glyphData.hasBaseText()) {
return;
}
- boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
- mBaseTextPaint));
+ GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
if (glyphData.hasValidShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
- mModifierTextPaint));
+ GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
}
if (glyphData.hasValidAltGrText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
}
- if (glyphData.hasValidAltShiftText()) {
+ if (glyphData.hasValidAltGrShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
}
@@ -246,15 +250,19 @@
float centerY = keyHeight / 2;
if ((glyph.gravity & GRAVITY_LEFT) != 0) {
centerX -= keyWidth / 4;
+ centerX += mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_RIGHT) != 0) {
centerX += keyWidth / 4;
+ centerX -= mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_TOP) != 0) {
centerY -= keyHeight / 4;
+ centerY += mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_BOTTOM) != 0) {
centerY += keyHeight / 4;
+ centerY -= mTextPadding / 2;
}
Rect textBounds = new Rect();
glyph.paint.getTextBounds(glyph.text, 0, glyph.text.length(), textBounds);
@@ -285,9 +293,9 @@
private static class UnsureTypingKey extends TypingKey {
private UnsureTypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData,
- RectF keyRect, float keyRadius, Paint keyPaint, Paint baseTextPaint,
- Paint modifierTextPaint) {
- super(glyphData, keyRect, keyRadius, createGreyedOutPaint(keyPaint),
+ RectF keyRect, float keyRadius, float textPadding, Paint keyPaint,
+ Paint baseTextPaint, Paint modifierTextPaint) {
+ super(glyphData, keyRect, keyRadius, textPadding, createGreyedOutPaint(keyPaint),
createGreyedOutPaint(baseTextPaint), createGreyedOutPaint(modifierTextPaint));
}
}
@@ -402,8 +410,11 @@
private final Paint mSecondaryGlyphPaint;
private final int mKeyPadding;
private final int mKeyboardPadding;
+ private final float mTextPadding;
private final float mKeyRadius;
private final float mBackgroundRadius;
+ private final float mSpToPxMultiplier;
+ private final Paint.FontMetrics mFontMetrics;
private ResourceProvider(Context context) {
mKeyPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
@@ -414,8 +425,10 @@
KEY_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
mBackgroundRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
KEYBOARD_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
- int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
- GLYPH_TEXT_SIZE_IN_SP, context.getResources().getDisplayMetrics());
+ mSpToPxMultiplier = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
+ context.getResources().getDisplayMetrics());
+ mTextPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ TEXT_PADDING_IN_DP, context.getResources().getDisplayMetrics());
boolean isDark = (context.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
int typingKeyColor = context.getColor(
@@ -430,15 +443,37 @@
int backgroundColor = context.getColor(
isDark ? android.R.color.system_surface_container_dark
: android.R.color.system_surface_container_light);
- mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor, textSize,
+ mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor,
+ MIN_GLYPH_TEXT_SIZE_IN_SP * mSpToPxMultiplier,
Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
- mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor, textSize,
+ mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor,
+ MIN_GLYPH_TEXT_SIZE_IN_SP * mSpToPxMultiplier,
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
+ mFontMetrics = mPrimaryGlyphPaint.getFontMetrics();
mTypingKeyPaint = createFillPaint(typingKeyColor);
mSpecialKeyPaint = createFillPaint(specialKeyColor);
mBackgroundPaint = createFillPaint(backgroundColor);
}
+ private void calculateBestTextSizeForKey(float keyHeight) {
+ int textSize = (int) (mSpToPxMultiplier * MIN_GLYPH_TEXT_SIZE_IN_SP) + 1;
+ while (textSize < mSpToPxMultiplier * MAX_GLYPH_TEXT_SIZE_IN_SP) {
+ updateTextSize(textSize);
+ if (mFontMetrics.bottom - mFontMetrics.top + 3 * mTextPadding > keyHeight / 2) {
+ textSize--;
+ break;
+ }
+ textSize++;
+ }
+ updateTextSize(textSize);
+ }
+
+ private void updateTextSize(float textSize) {
+ mPrimaryGlyphPaint.setTextSize(textSize);
+ mSecondaryGlyphPaint.setTextSize(textSize);
+ mPrimaryGlyphPaint.getFontMetrics(mFontMetrics);
+ }
+
private Paint getBackgroundPaint() {
return mBackgroundPaint;
}
@@ -467,6 +502,10 @@
return mKeyboardPadding;
}
+ private float getTextPadding() {
+ return mTextPadding;
+ }
+
private float getKeyRadius() {
return mKeyRadius;
}
@@ -476,7 +515,8 @@
}
}
- private static Paint createTextPaint(@ColorInt int textColor, int textSize, Typeface typeface) {
+ private static Paint createTextPaint(@ColorInt int textColor, float textSize,
+ Typeface typeface) {
Paint paint = new Paint();
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 844e02f..cff444f 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -336,11 +336,13 @@
return "";
}
int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
+ if (utf8Char == 0) {
+ return "";
+ }
if (Character.isValidCodePoint(utf8Char)) {
return String.valueOf(Character.toChars(utf8Char));
- } else {
- return String.valueOf(kcm.getDisplayLabel(keyCode));
}
+ return "□";
}
private static LayoutKey getKey(int keyCode, float keyWeight) {
@@ -434,10 +436,11 @@
}
public boolean hasValidAltGrText() {
- return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
+ return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText)
+ && !TextUtils.equals(mShiftText, mAltGrText);
}
- public boolean hasValidAltShiftText() {
+ public boolean hasValidAltGrShiftText() {
return !TextUtils.isEmpty(mAltGrShiftText)
&& !TextUtils.equals(mBaseText, mAltGrShiftText)
&& !TextUtils.equals(mAltGrText, mAltGrShiftText)
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index 6eb2ae3..6a7d195 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.companion.virtual.IVirtualDevice;
import android.os.IBinder;
import android.os.RemoteException;
@@ -66,4 +68,15 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * @return The id of the {@link android.view.InputDevice} corresponding to this keyboard.
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ @Override
+ public int getInputDeviceId() {
+ return super.getInputDeviceId();
+ }
}
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
index ff762d7..d679f9c 100644
--- a/core/java/android/net/thread/flags.aconfig
+++ b/core/java/android/net/thread/flags.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.net.thread.flags"
+package: "com.android.net.thread.platform.flags"
# This file contains aconfig flags used from platform code
# Flags used for module APIs must be in aconfig files under each modules
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 4b170f3..b1c24a7 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -87,6 +87,7 @@
# PerformanceHintManager
per-file PerformanceHintManager.java = file:/ADPF_OWNERS
+per-file WorkDuration.java = file:/ADPF_OWNERS
# IThermal interfaces
per-file IThermal* = file:/THERMAL_OWNERS
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 91d2269..3cc6fb5a 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -24,6 +24,7 @@
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
/**
* PermissionEnforcer check permissions for AIDL-generated services which use
@@ -71,6 +72,7 @@
* @hide
*/
@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PermissionEnforcer {
private final Context mContext;
@@ -84,6 +86,8 @@
}
/** Constructor, prefer using the fromContext static method when possible */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = PermissionManager.class,
+ reason = "Use subclass for unit tests, such as FakePermissionEnforcer")
public PermissionEnforcer(@NonNull Context context) {
mContext = context;
}
@@ -103,9 +107,19 @@
return PermissionCheckerManager.PERMISSION_HARD_DENIED;
}
+ @android.ravenwood.annotation.RavenwoodReplace(blockedBy = AppOpsManager.class,
+ reason = "Blocked on Mainline dependencies")
+ private static int permissionToOpCode(String permission) {
+ return AppOpsManager.permissionToOpCode(permission);
+ }
+
+ private static int permissionToOpCode$ravenwood(String permission) {
+ return AppOpsManager.OP_NONE;
+ }
+
private boolean anyAppOps(@NonNull String[] permissions) {
for (String permission : permissions) {
- if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ if (permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
return true;
}
}
@@ -122,7 +136,7 @@
public void enforcePermission(@NonNull String permission, int pid, int uid)
throws SecurityException {
- if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ if (permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
AttributionSource source = new AttributionSource(uid, null, null);
enforcePermission(permission, source);
return;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e96c24d..0be2d3e3 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BinderInternal;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.StatLogger;
import java.util.Map;
@@ -38,6 +39,7 @@
* @hide
**/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public final class ServiceManager {
private static final String TAG = "ServiceManager";
private static final Object sLock = new Object();
@@ -48,9 +50,16 @@
/**
* Cache for the "well known" services, such as WM and AM.
*/
+ // NOTE: this cache is designed to be populated exactly once at process
+ // start to avoid any overhead from locking
@UnsupportedAppUsage
private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
+ @GuardedBy("ServiceManager.class")
+ // NOTE: this cache is designed to support mutation by tests, so we require
+ // a lock to be held for all accesses
+ private static Map<String, IBinder> sCache$ravenwood;
+
/**
* We do the "slow log" at most once every this interval.
*/
@@ -115,9 +124,27 @@
/** @hide */
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public ServiceManager() {
}
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void init$ravenwood() {
+ synchronized (ServiceManager.class) {
+ sCache$ravenwood = new ArrayMap<>();
+ }
+ }
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void reset$ravenwood() {
+ synchronized (ServiceManager.class) {
+ sCache$ravenwood.clear();
+ sCache$ravenwood = null;
+ }
+ }
+
@UnsupportedAppUsage
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
@@ -138,6 +165,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodReplace
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
@@ -152,12 +180,21 @@
return null;
}
+ /** @hide */
+ public static IBinder getService$ravenwood(String name) {
+ synchronized (ServiceManager.class) {
+ // Ravenwood is a single-process environment, so it only needs to store locally
+ return Preconditions.requireNonNullViaRavenwoodRule(sCache$ravenwood).get(name);
+ }
+ }
+
/**
* Returns a reference to a service with the given name, or throws
* {@link ServiceNotFoundException} if none is found.
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
final IBinder binder = getService(name);
if (binder != null) {
@@ -176,6 +213,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public static void addService(String name, IBinder service) {
addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -191,6 +229,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public static void addService(String name, IBinder service, boolean allowIsolated) {
addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -207,6 +246,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodReplace
public static void addService(String name, IBinder service, boolean allowIsolated,
int dumpPriority) {
try {
@@ -216,6 +256,15 @@
}
}
+ /** @hide */
+ public static void addService$ravenwood(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ synchronized (ServiceManager.class) {
+ // Ravenwood is a single-process environment, so it only needs to store locally
+ Preconditions.requireNonNullViaRavenwoodRule(sCache$ravenwood).put(name, service);
+ }
+ }
+
/**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
@@ -366,6 +415,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class ServiceNotFoundException extends Exception {
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7873a76..be17d7c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1920,7 +1920,9 @@
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- @FlaggedApi(com.android.net.thread.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED)
+ // TODO (b/325886480): update the flag to
+ // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED"
+ @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
/**
diff --git a/core/java/android/service/chooser/ChooserResult.java b/core/java/android/service/chooser/ChooserResult.java
index 4603be1..2d56ec7 100644
--- a/core/java/android/service/chooser/ChooserResult.java
+++ b/core/java/android/service/chooser/ChooserResult.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.Overridable;
@@ -91,6 +92,7 @@
}
/** @hide */
+ @TestApi
public ChooserResult(@ResultType int type, @Nullable ComponentName componentName,
boolean isShortcut) {
mType = type;
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
new file mode 100644
index 0000000..bbb4bc6
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ICancellationSignal;
+import android.os.RemoteCallback;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import com.android.internal.infra.AndroidFuture;
+
+
+/**
+ * Interface for a concrete implementation to provide on device intelligence services.
+ *
+ * @hide
+ */
+oneway interface IOnDeviceIntelligenceService {
+ void getVersion(in RemoteCallback remoteCallback);
+ void getFeature(in int featureId, in IFeatureCallback featureCallback);
+ void listFeatures(in IListFeaturesCallback listFeaturesCallback);
+ void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+ void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
+ void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
+ void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
new file mode 100644
index 0000000..08eb927
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.Feature;
+import android.os.ICancellationSignal;
+import android.service.ondeviceintelligence.IRemoteStorageService;
+
+
+/**
+ * Interface for a concrete implementation to provide on device trusted inference.
+ *
+ * @hide
+ */
+oneway interface IOnDeviceTrustedInferenceService {
+ void registerRemoteStorageService(in IRemoteStorageService storageService);
+ void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ in ITokenCountCallback tokenCountCallback);
+ void processRequest(in Feature feature, in Content request, in int requestType,
+ in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
+ in IResponseCallback callback);
+ void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+ in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
+ in IStreamingResponseCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
new file mode 100644
index 0000000..a6f49e1
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Feature;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * Interface for a concrete implementation to provide access to storage read access
+ * for the isolated process.
+ *
+ * @hide
+ */
+oneway interface IRemoteStorageService {
+ void getReadOnlyFileDescriptor(in String filePath, in AndroidFuture<ParcelFileDescriptor> future);
+ void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
new file mode 100644
index 0000000..0cba1d3
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -0,0 +1,383 @@
+/*
+ * 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 android.service.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ondeviceintelligence.DownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.LongConsumer;
+
+/**
+ * Abstract base class for performing setup for on-device inference and providing file access to
+ * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ *
+ * <p> A service that provides configuration and model files relevant to performing inference on
+ * device. The system's default OnDeviceIntelligenceService implementation is configured in
+ * {@code config_defaultOnDeviceIntelligenceService}. If this config has no value, a stub is
+ * returned.
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".SampleOnDeviceIntelligenceService"
+ * android:permission="android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public abstract class OnDeviceIntelligenceService extends Service {
+ private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IOnDeviceIntelligenceService.Stub() {
+ /** {@inheritDoc} */
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) {
+ Objects.requireNonNull(remoteCallback);
+ OnDeviceIntelligenceService.this.onGetVersion(l -> {
+ Bundle b = new Bundle();
+ b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l);
+ remoteCallback.sendResult(b);
+ });
+ }
+
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+ Objects.requireNonNull(listFeaturesCallback);
+ OnDeviceIntelligenceService.this.onListFeatures(
+ wrapListFeaturesCallback(listFeaturesCallback));
+ }
+
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback) {
+ Objects.requireNonNull(featureCallback);
+ OnDeviceIntelligenceService.this.onGetFeature(id,
+ wrapFeatureCallback(featureCallback));
+ }
+
+
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+
+ OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
+ wrapFeatureDetailsCallback(featureDetailsCallback));
+ }
+
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+
+ OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+ CancellationSignal.fromTransport(cancellationSignal),
+ wrapDownloadCallback(downloadCallback));
+ }
+
+ @Override
+ public void getReadOnlyFileDescriptor(String fileName,
+ AndroidFuture<ParcelFileDescriptor> future) {
+ Objects.requireNonNull(fileName);
+ Objects.requireNonNull(future);
+
+ OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor(fileName,
+ future);
+ }
+
+ @Override
+ public void getReadOnlyFeatureFileDescriptorMap(
+ Feature feature, RemoteCallback remoteCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(remoteCallback);
+
+ OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap(
+ feature, parcelFileDescriptorMap -> {
+ Bundle bundle = new Bundle();
+ parcelFileDescriptorMap.forEach(bundle::putParcelable);
+ remoteCallback.sendResult(bundle);
+ });
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ private OutcomeReceiver<Feature,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ IFeatureCallback featureCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull Feature feature) {
+ try {
+ featureCallback.onSuccess(feature);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ try {
+ featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download feature: " + e);
+ }
+ }
+ };
+
+ }
+
+ private OutcomeReceiver<List<Feature>,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ IListFeaturesCallback listFeaturesCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull List<Feature> features) {
+ try {
+ listFeaturesCallback.onSuccess(features);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ try {
+ listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download feature: " + e);
+ }
+ }
+ };
+ }
+
+ private OutcomeReceiver<FeatureDetails,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ IFeatureDetailsCallback featureStatusCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(FeatureDetails result) {
+ try {
+ featureStatusCallback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature status: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ try {
+ featureStatusCallback.onFailure(exception.getErrorCode(),
+ exception.getMessage(), exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature status: " + e);
+ }
+ }
+ };
+ }
+
+
+ private DownloadCallback wrapDownloadCallback(IDownloadCallback downloadCallback) {
+ return new DownloadCallback() {
+ @Override
+ public void onDownloadStarted(long bytesToDownload) {
+ try {
+ downloadCallback.onDownloadStarted(bytesToDownload);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus,
+ String errorMessage, @NonNull PersistableBundle errorParams) {
+ try {
+ downloadCallback.onDownloadFailed(failureStatus, errorMessage, errorParams);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadProgress(long totalBytesDownloaded) {
+ try {
+ downloadCallback.onDownloadProgress(totalBytesDownloaded);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadCompleted(@NonNull PersistableBundle persistableBundle) {
+ try {
+ downloadCallback.onDownloadCompleted(persistableBundle);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+ };
+ }
+
+ private void onGetReadOnlyFileDescriptor(@NonNull String fileName,
+ @NonNull AndroidFuture<ParcelFileDescriptor> future) {
+ Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName);
+ Binder.withCleanCallingIdentity(() -> {
+ Slog.v(TAG,
+ "onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage.");
+ File f = new File(getBaseContext().getFilesDir(), fileName);
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
+ } finally {
+ future.complete(pfd);
+ }
+ });
+ }
+
+ /**
+ * Provide implementation for a scenario when caller wants to get all feature related
+ * file-descriptors that might be required for processing a request for the corresponding the
+ * feature.
+ *
+ * @param feature the feature for which files need to be opened.
+ * @param fileDescriptorMapConsumer callback to be populated with a map of file-path and
+ * corresponding ParcelDescriptor to be used in a remote
+ * service.
+ */
+ public abstract void onGetReadOnlyFeatureFileDescriptorMap(
+ @NonNull Feature feature,
+ @NonNull Consumer<Map<String, ParcelFileDescriptor>> fileDescriptorMapConsumer);
+
+ /**
+ * Request download for feature that is requested and listen to download progress updates. If
+ * the download completes successfully, success callback should be populated.
+ *
+ * @param feature the feature for which files need to be downlaoded.
+ * process.
+ * @param cancellationSignal signal to attach a listener to, and receive cancellation signals
+ * from thw client.
+ * @param downloadCallback callback to populate download updates for clients to listen on..
+ */
+ public abstract void onDownloadFeature(
+ @NonNull Feature feature,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull DownloadCallback downloadCallback);
+
+ /**
+ * Provide feature details for the passed in feature. Usually the client and remote
+ * implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
+ * details the client is looking for.
+ *
+ * @param feature the feature for which status needs to be known.
+ * @param featureStatusCallback callback to populate the resulting feature status.
+ */
+ public abstract void onGetFeatureDetails(@NonNull Feature feature,
+ @NonNull OutcomeReceiver<FeatureDetails,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+
+
+ /**
+ * Get feature using the provided identifier to the remote implementation.
+ *
+ * @param featureCallback callback to populate the features list.
+ */
+ public abstract void onGetFeature(int featureId,
+ @NonNull OutcomeReceiver<Feature,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+
+ /**
+ * List all features which are available in the remote implementation. The implementation might
+ * choose to provide only a certain list of features based on the caller.
+ *
+ * @param listFeaturesCallback callback to populate the features list.
+ */
+ public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+
+ /**
+ * Provides a long value representing the version of the remote implementation processing
+ * requests.
+ *
+ * @param versionConsumer consumer to populate the version.
+ */
+ public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
+}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
new file mode 100644
index 0000000..96982e3
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
@@ -0,0 +1,410 @@
+/*
+ * 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 android.service.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.ProcessingSignal;
+import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class for performing inference in a isolated process. This service exposes its
+ * methods via {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}.
+ *
+ * <p> A service that provides methods to perform on-device inference both in streaming and
+ * non-streaming fashion. Also, provides a way to register a storage service that will be used to
+ * read-only access files from the {@link OnDeviceIntelligenceService} counterpart. </p>
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".SampleTrustedInferenceService"
+ * android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * android:isolatedProcess="true">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public abstract class OnDeviceTrustedInferenceService extends Service {
+ private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+
+ private IRemoteStorageService mRemoteStorageService;
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IOnDeviceTrustedInferenceService.Stub() {
+ @Override
+ public void registerRemoteStorageService(IRemoteStorageService storageService) {
+ Objects.requireNonNull(storageService);
+ mRemoteStorageService = storageService;
+ }
+
+ @Override
+ public void requestTokenCount(Feature feature, Content request,
+ ICancellationSignal cancellationSignal,
+ ITokenCountCallback tokenCountCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(tokenCountCallback);
+ OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+ request,
+ CancellationSignal.fromTransport(cancellationSignal),
+ wrapTokenCountCallback(tokenCountCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature, Content request,
+ int requestType, ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback callback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(callback);
+
+ OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+ request,
+ requestType,
+ CancellationSignal.fromTransport(cancellationSignal),
+ ProcessingSignal.fromTransport(processingSignal),
+ wrapStreamingResponseCallback(callback)
+ );
+ }
+
+ @Override
+ public void processRequest(Feature feature, Content request,
+ int requestType, ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback callback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(callback);
+
+
+ OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
+ requestType, CancellationSignal.fromTransport(cancellationSignal),
+ ProcessingSignal.fromTransport(processingSignal),
+ wrapResponseCallback(callback)
+ );
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ /**
+ * Invoked when caller wants to obtain a count of number of tokens present in the passed in
+ * Request associated with the provided feature.
+ * The expectation from the implementation is that when processing is complete, it
+ * should provide the token count in the {@link OutcomeReceiver#onResult}.
+ *
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param callback callback to populate failure and full response for the provided
+ * request.
+ */
+ @NonNull
+ public abstract void onCountTokens(
+ @NonNull Feature feature,
+ @NonNull Content request,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull OutcomeReceiver<Long,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+
+ /**
+ * Invoked when caller provides a request for a particular feature to be processed in a
+ * streaming manner. The expectation from the implementation is that when processing the
+ * request,
+ * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+ * provide partial Content results for the caller to utilize. Optionally the implementation can
+ * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+ * processing completion.
+ *
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param requestType identifier representing the type of request.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param processingSignal Signal to receive custom action instructions from client.
+ * @param callback callback to populate the partial responses, failure and optionally
+ * full response for the provided request.
+ */
+ @NonNull
+ public abstract void onProcessRequestStreaming(
+ @NonNull Feature feature,
+ @NonNull Content request,
+ @OnDeviceIntelligenceManager.RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull StreamingResponseReceiver<Content, Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+
+ /**
+ * Invoked when caller provides a request for a particular feature to be processed in one shot
+ * completely.
+ * The expectation from the implementation is that when processing the request is complete, it
+ * should
+ * provide the complete response in the {@link OutcomeReceiver#onResult}.
+ *
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param requestType identifier representing the type of request.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param processingSignal Signal to receive custom action instructions from client.
+ * @param callback callback to populate failure and full response for the provided
+ * request.
+ */
+ @NonNull
+ public abstract void onProcessRequest(
+ @NonNull Feature feature,
+ @NonNull Content request,
+ @OnDeviceIntelligenceManager.RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+
+ /**
+ * Overrides {@link Context#openFileInput} to read files with the given file names under the
+ * internal app storage of the {@link OnDeviceIntelligenceService}, i.e., only files stored in
+ * {@link Context#getFilesDir()} can be opened.
+ */
+ @Override
+ public final FileInputStream openFileInput(@NonNull String filename) throws
+ FileNotFoundException {
+ try {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ mRemoteStorageService.getReadOnlyFileDescriptor(filename, future);
+ ParcelFileDescriptor pfd = future.get();
+ return new FileInputStream(pfd.getFileDescriptor());
+ } catch (RemoteException | ExecutionException | InterruptedException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ }
+
+ /**
+ * Provides read-only access to the internal app storage via the
+ * {@link OnDeviceIntelligenceService}. This is an asynchronous implementation for
+ * {@link #openFileInput(String)}.
+ *
+ * @param fileName File name relative to the {@link Context#getFilesDir()}.
+ * @param resultConsumer Consumer to populate the corresponding file stream in.
+ */
+ public final void openFileInputAsync(@NonNull String fileName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<FileInputStream> resultConsumer) throws FileNotFoundException {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ try {
+ mRemoteStorageService.getReadOnlyFileDescriptor(fileName, future);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ future.whenCompleteAsync((pfd, err) -> {
+ if (err != null) {
+ Log.e(TAG, "Failure when reading file: " + fileName + err);
+ executor.execute(() -> resultConsumer.accept(null));
+ } else {
+ executor.execute(
+ () -> resultConsumer.accept(new FileInputStream(pfd.getFileDescriptor())));
+ }
+ }, executor);
+ }
+
+ /**
+ * Provides access to all file streams required for feature via the
+ * {@link OnDeviceIntelligenceService}.
+ *
+ * @param feature Feature for which the associated files should be fetched.
+ * @param executor Executor to run the consumer callback on.
+ * @param resultConsumer Consumer to receive a map of filePath to the corresponding file input
+ * stream.
+ */
+ public final void fetchFeatureFileInputStreamMap(@NonNull Feature feature,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Map<String, FileInputStream>> resultConsumer) {
+ try {
+ mRemoteStorageService.getReadOnlyFeatureFileDescriptorMap(feature,
+ wrapResultReceiverAsReadOnly(resultConsumer, executor));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private RemoteCallback wrapResultReceiverAsReadOnly(
+ @NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
+ @NonNull Executor executor) {
+ return new RemoteCallback(result -> {
+ if (result == null) {
+ executor.execute(() -> resultConsumer.accept(new HashMap<>()));
+ } else {
+ Map<String, FileInputStream> bundleMap = new HashMap<>();
+ result.keySet().forEach(key -> {
+ ParcelFileDescriptor pfd = result.getParcelable(key,
+ ParcelFileDescriptor.class);
+ if (pfd != null) {
+ bundleMap.put(key, new FileInputStream(pfd.getFileDescriptor()));
+ }
+ });
+ executor.execute(() -> resultConsumer.accept(bundleMap));
+ }
+ });
+ }
+
+ private OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+ IResponseCallback callback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@androidx.annotation.NonNull Content response) {
+ try {
+ callback.onSuccess(response);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ try {
+ callback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+ };
+ }
+
+ private StreamingResponseReceiver<Content, Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+ IStreamingResponseCallback callback) {
+ return new StreamingResponseReceiver<>() {
+ @Override
+ public void onNewContent(@androidx.annotation.NonNull Content content) {
+ try {
+ callback.onNewContent(content);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onResult(@androidx.annotation.NonNull Content response) {
+ try {
+ callback.onSuccess(response);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ try {
+ callback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+ };
+ }
+
+ private OutcomeReceiver<Long,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
+ ITokenCountCallback tokenCountCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Long tokenCount) {
+ try {
+ tokenCountCallback.onSuccess(tokenCount);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ try {
+ tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending failure: " + e);
+ }
+ }
+ };
+ }
+}
diff --git a/core/java/android/util/TimingsTraceLog.java b/core/java/android/util/TimingsTraceLog.java
index 48a5cea..b4f4729 100644
--- a/core/java/android/util/TimingsTraceLog.java
+++ b/core/java/android/util/TimingsTraceLog.java
@@ -34,6 +34,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class TimingsTraceLog {
// Debug boot time for every step if it's non-user build.
private static final boolean DEBUG_BOOT_TIME = !Build.IS_USER;
diff --git a/core/java/android/view/KeyboardShortcutGroup.java b/core/java/android/view/KeyboardShortcutGroup.java
index 763ca26..c4c87ef 100644
--- a/core/java/android/view/KeyboardShortcutGroup.java
+++ b/core/java/android/view/KeyboardShortcutGroup.java
@@ -35,6 +35,8 @@
private final List<KeyboardShortcutInfo> mItems;
// The system group looks different UI wise.
private boolean mSystemGroup;
+ // The package name for the shortcut
+ private CharSequence mPackageName;
/**
* @param label The title to be used for this group, or null if there is none.
@@ -82,6 +84,7 @@
mLabel = source.readCharSequence();
source.readTypedList(mItems, KeyboardShortcutInfo.CREATOR);
mSystemGroup = source.readInt() == 1;
+ mPackageName = source.readCharSequence();
}
/**
@@ -105,6 +108,22 @@
}
/**
+ * @param packageName the name of the package associated with this shortcut.
+ * @hide
+ */
+ public void setPackageName(CharSequence packageName) {
+ mPackageName = packageName;
+ }
+
+ /**
+ * Return the package name of the app associated with this shortcut.
+ * @hide
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Adds an item to the existing list.
*
* @param item The item to be added.
@@ -123,6 +142,7 @@
dest.writeCharSequence(mLabel);
dest.writeTypedList(mItems);
dest.writeInt(mSystemGroup ? 1 : 0);
+ dest.writeCharSequence(mPackageName);
}
public static final @android.annotation.NonNull Creator<KeyboardShortcutGroup> CREATOR =
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8cbfdcb..cd6d79c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -78,6 +78,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -5357,16 +5358,16 @@
/**
* Flag indicating that an unhandled drag should be delegated to the system to be started if no
* visible window wishes to handle the drop. When using this flag, the caller must provide
- * ClipData with an Item that contains an immutable PendingIntent to an activity to be launched
+ * ClipData with an Item that contains an immutable IntentSender to an activity to be launched
* (not a broadcast, service, etc). See
- * {@link ClipData.Item.Builder#setPendingIntent(PendingIntent)}.
+ * {@link ClipData.Item.Builder#setIntentSender(IntentSender)}.
*
* The system can decide to launch the intent or not based on factors like the current screen
* size or windowing mode. If the system does not launch the intent, it will be canceled via the
* normal drag and drop flow.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
- public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 1 << 13;
+ public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 1 << 13;
/**
* Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
@@ -28665,10 +28666,10 @@
if (com.android.window.flags.Flags.delegateUnhandledDrags()) {
data.prepareToLeaveProcess(
(flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0);
- if ((flags & DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG) != 0) {
- if (!data.hasActivityPendingIntents()) {
+ if ((flags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ if (!hasActivityPendingIntents(data)) {
// Reset the flag if there is no launchable activity intent
- flags &= ~DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG;
+ flags &= ~DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
Log.w(VIEW_LOG_TAG, "startDragAndDrop called with "
+ "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data "
+ "contains non-activity PendingIntents");
@@ -28781,7 +28782,7 @@
mAttachInfo.mDragSurface.release();
}
if (mAttachInfo.mDragData != null) {
- mAttachInfo.mDragData.cleanUpPendingIntents();
+ View.cleanUpPendingIntents(mAttachInfo.mDragData);
}
mAttachInfo.mDragSurface = surface;
mAttachInfo.mDragToken = token;
@@ -28806,6 +28807,39 @@
}
}
+ /**
+ * Checks if this clip data has a pending intent that is an activity type.
+ * @hide
+ */
+ static boolean hasActivityPendingIntents(ClipData data) {
+ final int size = data.getItemCount();
+ for (int i = 0; i < size; i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());
+ if (pi.isActivity()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Cleans up all pending intents in the ClipData.
+ * @hide
+ */
+ static void cleanUpPendingIntents(ClipData data) {
+ final int size = data.getItemCount();
+ for (int i = 0; i < size; i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());
+ pi.cancel();
+ }
+ }
+ }
+
void setAccessibilityDragStarted(boolean started) {
int pflags4 = mPrivateFlags4;
if (started) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 75deceb..1e79786 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8626,7 +8626,7 @@
mAttachInfo.mDragSurface = null;
}
if (mAttachInfo.mDragData != null) {
- mAttachInfo.mDragData.cleanUpPendingIntents();
+ View.cleanUpPendingIntents(mAttachInfo.mDragData);
mAttachInfo.mDragData = null;
}
}
@@ -8652,6 +8652,12 @@
if (mView != null) {
mView.requestKeyboardShortcuts(list, deviceId);
}
+ int numGroups = list.size();
+ for (int i = 0; i < numGroups; ++i) {
+ final KeyboardShortcutGroup group = list.get(i);
+ group.setPackageName(mBasePackageName);
+
+ }
data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);
try {
receiver.send(0, data);
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bd9f504..83683ca 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2364,6 +2364,7 @@
synchronized (mLock) {
if (!isActiveLocked()) {
+ Log.w(TAG, "onAuthenticationResult(): sessionId=" + mSessionId + " not active");
return;
}
mState = STATE_ACTIVE;
@@ -2380,6 +2381,7 @@
}
if (data == null) {
// data is set to null when result is not RESULT_OK
+ Log.i(TAG, "onAuthenticationResult(): empty intent");
return;
}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index dc5e0e5..88ca2a4 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -66,6 +66,7 @@
@Nullable
private static volatile IImeTracker sTrackerServiceCache = null;
+ private static int sCurStartInputSeq = 0;
/**
* @return {@code true} if {@link IInputMethodManager} is available.
@@ -327,6 +328,7 @@
}
}
+ // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
@AnyThread
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@@ -353,6 +355,41 @@
}
}
+ /**
+ * Returns a sequence number for startInput.
+ */
+ @AnyThread
+ @NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+ static int startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason,
+ @NonNull IInputMethodClient client, @Nullable IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo,
+ @Nullable IRemoteInputConnection remoteInputConnection,
+ @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return -1;
+ }
+ try {
+ service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
+ remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
+ imeDispatcher, advanceAngGetStartInputSequenceNumber());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return sCurStartInputSeq;
+ }
+
+ private static int advanceAngGetStartInputSequenceNumber() {
+ return ++sCurStartInputSeq;
+ }
+
+
@AnyThread
static void showInputMethodPickerFromClient(@NonNull IInputMethodClient client,
int auxiliarySubtypeMode) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f4b09df..72125ba 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -321,6 +321,22 @@
};
/**
+ * A runnable that reports {@link InputConnection} opened event for calls to
+ * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}.
+ */
+ private abstract static class ReportInputConnectionOpenedRunner implements Runnable {
+ /**
+ * Sequence number to track startInput requests to
+ * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}
+ */
+ int mSequenceNum;
+ ReportInputConnectionOpenedRunner(int sequenceNum) {
+ this.mSequenceNum = sequenceNum;
+ }
+ }
+ private ReportInputConnectionOpenedRunner mReportInputConnectionOpenedRunner;
+
+ /**
* Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
* or indirectly relied on {@link #sInstance} via reflection or something like that.
*
@@ -691,6 +707,7 @@
private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
private static final int MSG_SET_INTERACTIVE = 13;
private static final int MSG_ON_SHOW_REQUESTED = 31;
+ private static final int MSG_START_INPUT_RESULT = 40;
/**
* Calling this will invalidate Local stylus handwriting availability Cache which
@@ -1045,7 +1062,7 @@
return;
}
case MSG_BIND: {
- final InputBindResult res = (InputBindResult)msg.obj;
+ final InputBindResult res = (InputBindResult) msg.obj;
if (DEBUG) {
Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
}
@@ -1071,6 +1088,60 @@
startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
return;
}
+
+ case MSG_START_INPUT_RESULT: {
+ final InputBindResult res = (InputBindResult) msg.obj;
+ final int startInputSeq = msg.arg1;
+ if (res == null) {
+ // IMMS logs .wtf already.
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+ synchronized (mH) {
+ if (res.id != null) {
+ updateInputChannelLocked(res.channel);
+ mCurMethod = res.method; // for @UnsupportedAppUsage
+ mCurBindState = new BindState(res);
+ mAccessibilityInputMethodSession.clear();
+ if (res.accessibilitySessions != null) {
+ for (int i = 0; i < res.accessibilitySessions.size(); i++) {
+ IAccessibilityInputMethodSessionInvoker wrapper =
+ IAccessibilityInputMethodSessionInvoker.createOrNull(
+ res.accessibilitySessions.valueAt(i));
+ if (wrapper != null) {
+ mAccessibilityInputMethodSession.append(
+ res.accessibilitySessions.keyAt(i), wrapper);
+ }
+ }
+ }
+ mCurId = res.id; // for @UnsupportedAppUsage
+ } else if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ switch (res.result) {
+ case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+ mRestartOnNextWindowFocus = true;
+ mServedView = null;
+ break;
+ }
+ if (mCompletions != null) {
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.displayCompletions(mCompletions);
+ }
+ }
+
+ if (res != null
+ && res.method != null
+ && mServedView != null
+ && mReportInputConnectionOpenedRunner != null
+ && mReportInputConnectionOpenedRunner.mSequenceNum
+ == startInputSeq) {
+ mReportInputConnectionOpenedRunner.run();
+ }
+ mReportInputConnectionOpenedRunner = null;
+ }
+ return;
+ }
case MSG_UNBIND: {
final int sequence = msg.arg1;
@UnbindReason
@@ -1322,6 +1393,12 @@
}
@Override
+ public void onStartInputResult(InputBindResult res, int startInputSeq) {
+ mH.obtainMessage(MSG_START_INPUT_RESULT, startInputSeq, -1 /* unused */, res)
+ .sendToTarget();
+ }
+
+ @Override
public void onBindAccessibilityService(InputBindResult res, int id) {
mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget();
}
@@ -2010,6 +2087,7 @@
mServedConnecting = false;
clearConnectionLocked();
}
+ mReportInputConnectionOpenedRunner = null;
// Clear the back callbacks held by the ime dispatcher to avoid memory leaks.
mImeDispatcher.clear();
}
@@ -3080,14 +3158,52 @@
final int targetUserId = editorInfo.targetInputMethodUser != null
? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");
- res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
- startInputReason, mClient, windowGainingFocus, startInputFlags,
- softInputMode, windowFlags, editorInfo, servedInputConnection,
- servedInputConnection == null ? null
- : servedInputConnection.asIRemoteAccessibilityInputConnection(),
- view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher);
+
+ int startInputSeq = -1;
+ if (Flags.useZeroJankProxy()) {
+ // async result delivered via MSG_START_INPUT_RESULT.
+ startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, editorInfo, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
+ mImeDispatcher);
+ } else {
+ res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, editorInfo, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
+ mImeDispatcher);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (Flags.useZeroJankProxy()) {
+ // Create a runnable for delayed notification to the app that the InputConnection is
+ // initialized and ready for use.
+ if (ic != null) {
+ final int seqId = startInputSeq;
+ mReportInputConnectionOpenedRunner =
+ new ReportInputConnectionOpenedRunner(startInputSeq) {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.v(TAG, "Calling View.onInputConnectionOpened: view= "
+ + view
+ + ", ic=" + ic + ", editorInfo=" + editorInfo
+ + ", handler="
+ + icHandler + ", startInputSeq=" + seqId);
+ }
+ reportInputConnectionOpened(ic, editorInfo, icHandler, view);
+ }
+ };
+ } else {
+ mReportInputConnectionOpenedRunner = null;
+ }
+ return true;
+ }
+
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
@@ -3118,6 +3234,7 @@
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
+
switch (res.result) {
case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
mRestartOnNextWindowFocus = true;
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index d0ed8ee..7dd7719 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -137,10 +137,6 @@
properties.getBoolean(
LOCAL_TEXT_CLASSIFIER_ENABLED,
LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
- sSystemTextClassifierEnabled =
- properties.getBoolean(
- SYSTEM_TEXT_CLASSIFIER_ENABLED,
- SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
sModelDarkLaunchEnabled =
properties.getBoolean(
MODEL_DARK_LAUNCH_ENABLED,
@@ -199,8 +195,11 @@
}
public boolean isSystemTextClassifierEnabled() {
- ensureMemoizedValues();
- return sSystemTextClassifierEnabled;
+ // Don't memoize this value because we want to be able to receive config
+ // updates at runtime.
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ SYSTEM_TEXT_CLASSIFIER_ENABLED,
+ SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
}
public boolean isModelDarkLaunchEnabled() {
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
index fa81ee3..3fa3760 100644
--- a/core/java/android/window/flags/OWNERS
+++ b/core/java/android/window/flags/OWNERS
@@ -1 +1,2 @@
-per-file responsible_apis.aconfig = file:/BAL_OWNERS
\ No newline at end of file
+per-file responsible_apis.aconfig = file:/BAL_OWNERS
+per-file large_screen_experiences_app_compat.aconfig = file:/LSE_APP_COMPAT_OWNERS
diff --git a/core/java/com/android/internal/colorextraction/OWNERS b/core/java/com/android/internal/colorextraction/OWNERS
index ffade1e..041559c 100644
--- a/core/java/com/android/internal/colorextraction/OWNERS
+++ b/core/java/com/android/internal/colorextraction/OWNERS
@@ -1,3 +1,2 @@
-dupin@google.com
cinek@google.com
-jamesoleary@google.com
+arteiro@google.com
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
index 9251d2d..babd9a0 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
@@ -24,6 +24,7 @@
*/
oneway interface IInputMethodClient {
void onBindMethod(in InputBindResult res);
+ void onStartInputResult(in InputBindResult res, int startInputSeq);
void onBindAccessibilityService(in InputBindResult res, int id);
void onUnbindMethod(int sequence, int unbindReason);
void onUnbindAccessibilityService(int sequence, int id);
diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java
index b6eca07..243b103 100644
--- a/core/java/com/android/internal/inputmethod/InputBindResult.java
+++ b/core/java/com/android/internal/inputmethod/InputBindResult.java
@@ -271,6 +271,7 @@
public String toString() {
return "InputBindResult{result=" + getResultString() + " method=" + method + " id=" + id
+ " sequence=" + sequence
+ + " result=" + result
+ " isInputMethodSuppressingSpellChecker=" + isInputMethodSuppressingSpellChecker
+ "}";
}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 48c455a..3662d69 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -120,7 +120,7 @@
public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
- public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
+ // 87 is reserved - previously assigned to deprecated CUJ_LAUNCHER_SEARCH_QSB_OPEN.
public static final int CUJ_BACK_PANEL_ARROW = 88;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK = 89;
public static final int CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH = 90;
@@ -209,7 +209,6 @@
CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
CUJ_PREDICTIVE_BACK_CROSS_TASK,
CUJ_PREDICTIVE_BACK_HOME,
- CUJ_LAUNCHER_SEARCH_QSB_OPEN,
CUJ_BACK_PANEL_ARROW,
CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK,
CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH,
@@ -304,7 +303,6 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_WEB_SEARCH;
@@ -480,8 +478,6 @@
return "PREDICTIVE_BACK_CROSS_TASK";
case CUJ_PREDICTIVE_BACK_HOME:
return "PREDICTIVE_BACK_HOME";
- case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
- return "LAUNCHER_SEARCH_QSB_OPEN";
case CUJ_BACK_PANEL_ARROW:
return "BACK_PANEL_ARROW";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK:
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index e95127b..b90f8bf 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -69,6 +69,8 @@
boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
in @nullable ImeTracker.Token statsToken, int flags,
in @nullable ResultReceiver resultReceiver, int reason);
+
+ // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
@@ -85,6 +87,21 @@
int unverifiedTargetSdkVersion, int userId,
in ImeOnBackInvokedDispatcher imeDispatcher);
+ // If windowToken is null, this just does startInput(). Otherwise this reports that a window
+ // has gained focus, and if 'editorInfo' is non-null then also does startInput.
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ void startInputOrWindowGainedFocusAsync(
+ /* @StartInputReason */ int startInputReason,
+ in IInputMethodClient client, in @nullable IBinder windowToken,
+ /* @StartInputFlags */ int startInputFlags,
+ /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
+ /* @android.view.WindowManager.LayoutParams.Flags */ int windowFlags,
+ in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
+ in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, int userId,
+ in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
@@ -156,6 +173,7 @@
in String delegatePackageName,
in String delegatorPackageName);
+ // TODO(b/293640003): introduce a new API method to provide async way to return boolean.
/** Accepts and starts a stylus handwriting session for the delegate view **/
boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, in int userId,
in String delegatePackageName, in String delegatorPackageName, int flags);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index a7260bb..c34730f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -49,8 +49,13 @@
this.mRemoteComposeState = remoteComposeState;
}
- public void reset() {
- mBuffer.reset();
+ /**
+ * Reset the internal buffers
+ *
+ * @param expectedSize provided hint for the main buffer size
+ */
+ public void reset(int expectedSize) {
+ mBuffer.reset(expectedSize);
mRemoteComposeState.reset();
}
@@ -288,8 +293,7 @@
public static void read(InputStream fd, RemoteComposeBuffer buffer) {
try {
byte[] bytes = readAllBytes(fd);
- buffer.reset();
- buffer.mBuffer.resize(bytes.length);
+ buffer.reset(bytes.length);
System.arraycopy(bytes, 0, buffer.mBuffer.mBuffer, 0, bytes.length);
buffer.mBuffer.mSize = bytes.length;
} catch (Exception e) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index 4518d94..b7cb392 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -83,10 +83,18 @@
mIndex = currentIndex;
}
- public void reset() {
+ /**
+ * Reset the internal buffer
+ *
+ * @param expectedSize provided hint for the buffer size
+ */
+ public void reset(int expectedSize) {
mIndex = 0;
mStartingIndex = 0;
mSize = 0;
+ if (expectedSize > mMaxSize) {
+ resize(expectedSize);
+ }
}
public int size() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 4bfdc59..76b7144 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -33,7 +33,7 @@
int mImageWidth;
int mImageHeight;
byte[] mBitmap;
- public static final int MAX_IMAGE_DIMENSION = 6000;
+ public static final int MAX_IMAGE_DIMENSION = 8000;
public static final Companion COMPANION = new Companion();
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 1d1f88b..3607865 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -55,6 +55,7 @@
optional string data = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional string type = 4;
optional string flag = 5;
+ optional string extended_flag = 14;
optional string package = 6;
optional ComponentNameProto component = 7;
optional string source_bounds = 8;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0b3a065..710b5f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7182,10 +7182,10 @@
<permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to manage the content suggestions service.
+ <!-- @SystemApi Allows an application to interact with the content suggestions service.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi Allows an application to manage the app predictions service.
@hide <p>Not for use by third-party applications.</p> -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c6bc589..1f06b0b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4670,6 +4670,13 @@
-->
<string name="config_defaultWearableSensingService" translatable="false"></string>
+
+ <!-- The component name for the default system on-device intelligence service, -->
+ <string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
+
+ <!-- The component name for the default system on-device trusted inference service. -->
+ <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
<string translatable="false" name="config_defaultWearableSensingConsentComponent"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9d7acff..cf9c02a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3909,6 +3909,8 @@
<java-symbol type="string" name="config_ambientContextPackageNameExtraKey" />
<java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
<java-symbol type="string" name="config_defaultWearableSensingService" />
+ <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
+ <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 861f719..37e6780 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -211,6 +211,7 @@
],
srcs: [
"src/android/app/ActivityManagerTest.java",
+ "src/android/content/ContextTest.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 48ef7e6..ebf4cca 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -43,7 +43,6 @@
import android.app.PictureInPictureUiState;
import android.app.ResourcesManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
-import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
@@ -75,7 +74,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.ReferrerIntent;
-import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -230,7 +228,7 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -247,7 +245,7 @@
newConfig.seq++;
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), newConfig));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -448,16 +446,16 @@
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigLandscape));
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
processConfigPortrait, DEVICE_ID_INVALID));
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigPortrait));
appThread.scheduleTransaction(transaction);
@@ -847,8 +845,8 @@
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, callbackItem);
- addClientTransactionItem(transaction, resumeStateRequest);
+ transaction.addTransactionItem(callbackItem);
+ transaction.addTransactionItem(resumeStateRequest);
return transaction;
}
@@ -860,7 +858,7 @@
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, resumeStateRequest);
+ transaction.addTransactionItem(resumeStateRequest);
return transaction;
}
@@ -871,7 +869,7 @@
activity.getActivityToken(), 0 /* configChanges */);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, stopStateRequest);
+ transaction.addTransactionItem(stopStateRequest);
return transaction;
}
@@ -883,7 +881,7 @@
activity.getActivityToken(), config);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, item);
+ transaction.addTransactionItem(item);
return transaction;
}
@@ -895,7 +893,7 @@
resume);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, item);
+ transaction.addTransactionItem(item);
return transaction;
}
@@ -910,17 +908,6 @@
return ClientTransaction.obtain(activityThread.getApplicationThread());
}
- private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
- @NonNull ClientTransactionItem item) {
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(item);
- } else if (item.isActivityLifecycleItem()) {
- transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
- } else {
- transaction.addCallback(item);
- }
- }
-
// Test activity
public static class TestActivity extends Activity {
static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 95d5049..213fd7b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -16,10 +16,13 @@
package android.app.servertransaction;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManager;
@@ -28,12 +31,14 @@
import android.os.Handler;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -49,6 +54,10 @@
@SmallTest
@Presubmit
public class ClientTransactionListenerControllerTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private IDisplayManager mIDisplayManager;
@Mock
@@ -60,12 +69,12 @@
@Before
public void setup() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
MockitoAnnotations.initMocks(this);
mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
mHandler = getInstrumentation().getContext().getMainThreadHandler();
- mController = spy(ClientTransactionListenerController.createInstanceForTesting(
- mDisplayManager));
- doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
+ mController = ClientTransactionListenerController.createInstanceForTesting(mDisplayManager);
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index d10cf16..5272416 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -16,16 +16,22 @@
package android.app.servertransaction;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ClientTransactionHandler;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,31 +49,28 @@
@Presubmit
public class ClientTransactionTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Test
public void testPreExecute() {
- final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
- final ClientTransactionHandler clientTransactionHandler =
- mock(ClientTransactionHandler.class);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
- transaction.setLifecycleStateRequest(stateRequest);
-
- transaction.preExecute(clientTransactionHandler);
-
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ testPreExecuteInner();
}
@Test
- public void testPreExecuteTransactionItems() {
+ public void testPreExecute_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testPreExecuteInner();
+ }
+
+ private void testPreExecuteInner() {
final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ doReturn(true).when(stateRequest).isActivityLifecycleItem();
final ClientTransactionHandler clientTransactionHandler =
mock(ClientTransactionHandler.class);
@@ -78,8 +81,8 @@
transaction.preExecute(clientTransactionHandler);
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ verify(callback1).preExecute(clientTransactionHandler);
+ verify(callback2).preExecute(clientTransactionHandler);
+ verify(stateRequest).preExecute(clientTransactionHandler);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 2315a58..adb6f2a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -25,6 +25,9 @@
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -51,12 +54,14 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -83,6 +88,9 @@
@Presubmit
public class TransactionExecutorTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private ClientTransactionHandler mTransactionHandler;
@Mock
@@ -240,29 +248,19 @@
@Test
public void testTransactionResolution() {
- ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
- ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
- transaction.setLifecycleStateRequest(mActivityLifecycleItem);
-
- transaction.preExecute(mTransactionHandler);
- mExecutor.execute(transaction);
-
- InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2,
- mActivityLifecycleItem);
- inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
- inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
- inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
- any());
+ testTransactionResolutionInner();
}
@Test
- public void testExecuteTransactionItems_transactionResolution() {
+ public void testTransactionResolution_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testTransactionResolutionInner();
+ }
+
+ private void testTransactionResolutionInner() {
ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
@@ -286,38 +284,19 @@
@Test
public void testDoNotLaunchDestroyedActivity() {
- final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
- when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
- // Assume launch transaction is still in queue, so there is no client record.
- when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- // An incoming destroy transaction enters binder thread (preExecute).
- final IBinder token = mock(IBinder.class);
- final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
- destroyTransaction.setLifecycleStateRequest(
- DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
- destroyTransaction.preExecute(mTransactionHandler);
- // The activity should be added to to-be-destroyed container.
- assertEquals(1, activitiesToBeDestroyed.size());
-
- // A previous queued launch transaction runs on main thread (execute).
- final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
- final LaunchActivityItem launchItem =
- spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
- launchTransaction.addCallback(launchItem);
- mExecutor.execute(launchTransaction);
-
- // The launch transaction should not be executed because its token is in the
- // to-be-destroyed container.
- verify(launchItem, never()).execute(any(), any());
-
- // After the destroy transaction has been executed, the token should be removed.
- mExecutor.execute(destroyTransaction);
- assertTrue(activitiesToBeDestroyed.isEmpty());
+ testDoNotLaunchDestroyedActivityInner();
}
@Test
- public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+ public void testDoNotLaunchDestroyedActivity_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testDoNotLaunchDestroyedActivityInner();
+ }
+
+ private void testDoNotLaunchDestroyedActivityInner() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
// Assume launch transaction is still in queue, so there is no client record.
@@ -350,26 +329,19 @@
@Test
public void testActivityResultRequiredStateResolution() {
- when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- PostExecItem postExecItem = new PostExecItem(ON_RESUME);
-
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(postExecItem);
-
- // Verify resolution that should get to onPause
- mClientRecord.setState(ON_RESUME);
- mExecutor.executeCallbacks(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
-
- // Verify resolution that should get to onStart
- mClientRecord.setState(ON_STOP);
- mExecutor.executeCallbacks(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ testActivityResultRequiredStateResolutionInner();
}
@Test
- public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+ public void testActivityResultRequiredStateResolution_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityResultRequiredStateResolutionInner();
+ }
+
+ private void testActivityResultRequiredStateResolutionInner() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
@@ -379,12 +351,12 @@
// Verify resolution that should get to onPause
mClientRecord.setState(ON_RESUME);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
// Verify resolution that should get to onStart
mClientRecord.setState(ON_STOP);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
}
@@ -523,18 +495,19 @@
@Test(expected = IllegalArgumentException.class)
public void testActivityItemNullRecordThrowsException() {
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- final IBinder token = mock(IBinder.class);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(activityItem);
- when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- mExecutor.executeCallbacks(transaction);
+ testActivityItemNullRecordThrowsExceptionInner();
}
@Test(expected = IllegalArgumentException.class)
- public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+ public void testActivityItemNullRecordThrowsException_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityItemNullRecordThrowsExceptionInner();
+ }
+
+ private void testActivityItemNullRecordThrowsExceptionInner() {
final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
final IBinder token = mock(IBinder.class);
@@ -542,28 +515,24 @@
transaction.addTransactionItem(activityItem);
when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
}
@Test
public void testActivityItemExecute() {
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- when(activityItem.getActivityToken()).thenReturn(mActivityToken);
- transaction.addCallback(activityItem);
- transaction.setLifecycleStateRequest(mActivityLifecycleItem);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- mExecutor.execute(transaction);
-
- final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem);
- inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
- inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
- any());
+ testActivityItemExecuteInner();
}
@Test
- public void testExecuteTransactionItems_activityItemExecute() {
+ public void testActivityItemExecute_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityItemExecuteInner();
+ }
+
+ private void testActivityItemExecuteInner() {
final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index c30d216..aa80013 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -20,6 +20,9 @@
import static android.app.servertransaction.TestUtils.mergedConfig;
import static android.app.servertransaction.TestUtils.referrerIntentList;
import static android.app.servertransaction.TestUtils.resultInfoList;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertEquals;
@@ -36,11 +39,13 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,6 +65,9 @@
@Presubmit
public class TransactionParcelTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private Parcel mParcel;
private IBinder mActivityToken;
@@ -275,6 +283,8 @@
@Test
public void testClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
@@ -300,14 +310,16 @@
@Test
public void testClientTransactionCallbacksOnly() {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
mActivityToken, config());
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
writeAndPrepareForReading(transaction);
@@ -321,12 +333,14 @@
@Test
public void testClientTransactionLifecycleOnly() {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.setLifecycleStateRequest(lifecycleRequest);
+ transaction.addTransactionItem(lifecycleRequest);
writeAndPrepareForReading(transaction);
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index d478437..a02af78 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.ActivityThread;
@@ -35,7 +36,9 @@
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.os.UserHandle;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.view.Display;
import androidx.test.core.app.ApplicationProvider;
@@ -43,6 +46,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +58,23 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ContextTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+
@Test
+ public void testInstrumentationContext() {
+ // Confirm that we have a valid Context
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getContext());
+ }
+
+ @Test
+ public void testInstrumentationTargetContext() {
+ // Confirm that we have a valid Context
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ }
+
+ @Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForSystemContext() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -63,6 +83,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForSystemUiContext() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -71,6 +92,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForTestContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -79,6 +101,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForDefaultDisplayContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -91,6 +114,7 @@
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNullIntentNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -98,6 +122,7 @@
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNullIntentNonNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -105,6 +130,7 @@
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNonNullIntentNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -112,6 +138,7 @@
}
@Test(expected = RuntimeException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNonNullIntentNonNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -119,6 +146,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_appContext_returnsFalse() {
final Context appContext = ApplicationProvider.getApplicationContext();
@@ -126,6 +154,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_systemContext_returnsTrue() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -134,6 +163,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_systemUiContext_returnsTrue() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -142,11 +172,13 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testGetDisplayFromDisplayContextDerivedContextOnSecondaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
}
@@ -179,6 +211,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_ContextWrapper() {
ContextWrapper wrapper = new ContextWrapper(null /* base */);
@@ -190,6 +223,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_UiContextDerivedContext() {
final Context uiContext = createUiContext();
Context context = uiContext.createAttributionContext(null /* attributionTag */);
@@ -202,6 +236,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_UiContextDerivedDisplayContext() {
final Context uiContext = createUiContext();
final Display secondaryDisplay =
@@ -212,6 +247,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForSystemContext() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -220,6 +256,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForSystemUiContext() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -228,6 +265,7 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForTestContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
index a567b4b..20a8768 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
@@ -28,6 +28,7 @@
import org.junit.runner.RunWith;
import java.util.function.Consumer;
+import java.util.function.Predicate;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -74,6 +75,13 @@
.isEqualTo(1));
}
+ @Test
+ public void runtimeMutableSettings() {
+ assertOverride(
+ TextClassificationConstants.SYSTEM_TEXT_CLASSIFIER_ENABLED,
+ settings -> settings.isSystemTextClassifierEnabled());
+ }
+
private static void assertSettings(
String key, String value, Consumer<TextClassificationConstants> settingsConsumer) {
final String originalValue =
@@ -87,6 +95,21 @@
}
}
+ private static void assertOverride(
+ String key, Predicate<TextClassificationConstants> settingsPredicate) {
+ final String originalValue =
+ DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key);
+ TextClassificationConstants settings = new TextClassificationConstants();
+ try {
+ setDeviceConfig(key, "true");
+ assertThat(settingsPredicate.test(settings)).isTrue();
+ setDeviceConfig(key, "false");
+ assertThat(settingsPredicate.test(settings)).isFalse();
+ } finally {
+ setDeviceConfig(key, originalValue);
+ }
+ }
+
private static void setDeviceConfig(String key, String value) {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, value, /* makeDefault */ false);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 310300d..d66c925 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -206,6 +206,8 @@
srcs: [
"multivalentTests/src/**/*.kt",
],
+ // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
+ exclude_srcs: ["multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
static_libs: [
"junit",
"androidx.test.runner",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 5825bbf..9cd14fca 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -466,6 +466,26 @@
.isEqualTo(expectedExpandedViewY)
}
+ @Test
+ fun testGetTaskViewContentWidth_onLeft() {
+ positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
+ val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */)
+ val paddings = positioner.getExpandedViewContainerPadding(true /* onLeft */,
+ false /* isOverflow */)
+ assertThat(taskViewWidth).isEqualTo(
+ positioner.screenRect.width() - paddings[0] - paddings[2])
+ }
+
+ @Test
+ fun testGetTaskViewContentWidth_onRight() {
+ positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
+ val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */)
+ val paddings = positioner.getExpandedViewContainerPadding(false /* onLeft */,
+ false /* isOverflow */)
+ assertThat(taskViewWidth).isEqualTo(
+ positioner.screenRect.width() - paddings[0] - paddings[2])
+ }
+
private val defaultYPosition: Float
/**
* Calculates the Y position bubbles should be placed based on the config. Based on the
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
new file mode 100644
index 0000000..8989fc5
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.view.IWindowManager
+import android.view.WindowManager
+import android.view.WindowManagerGlobal
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.common.ProtoLog
+import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/** Unit tests for [BubbleStackView]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleStackViewTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var positioner: BubblePositioner
+ private lateinit var iconFactory: BubbleIconFactory
+ private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
+ private lateinit var bubbleStackView: BubbleStackView
+ private lateinit var shellExecutor: ShellExecutor
+ private lateinit var windowManager: IWindowManager
+ private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
+ private lateinit var bubbleData: BubbleData
+
+ @Before
+ fun setUp() {
+ // Disable protolog tool when running the tests from studio
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ windowManager = WindowManagerGlobal.getWindowManagerService()!!
+ shellExecutor = TestShellExecutor()
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ iconFactory =
+ BubbleIconFactory(
+ context,
+ context.resources.getDimensionPixelSize(R.dimen.bubble_size),
+ context.resources.getDimensionPixelSize(R.dimen.bubble_badge_size),
+ Color.BLACK,
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width
+ )
+ )
+ positioner = BubblePositioner(context, windowManager)
+ val bubbleStackViewManager = FakeBubbleStackViewManager()
+ bubbleData =
+ BubbleData(
+ context,
+ BubbleLogger(UiEventLoggerFake()),
+ positioner,
+ BubbleEducationController(context),
+ shellExecutor
+ )
+
+ val sysuiProxy = mock<SysuiProxy>()
+ expandedViewManager = FakeBubbleExpandedViewManager()
+ bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ positioner,
+ bubbleData,
+ null,
+ FloatingContentCoordinator(),
+ { sysuiProxy },
+ shellExecutor
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun addBubble() {
+ val bubble = createAndInflateBubble()
+ bubbleStackView.addBubble(bubble)
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun tapBubbleToExpand() {
+ val bubble = createAndInflateBubble()
+ bubbleStackView.addBubble(bubble)
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ bubble.iconView!!.performClick()
+ // we're checking the expanded state in BubbleData because that's the source of truth. This
+ // will eventually propagate an update back to the stack view, but setting the entire
+ // pipeline is outside the scope of a unit test.
+ assertThat(bubbleData.isExpanded).isTrue()
+ }
+
+ private fun createAndInflateBubble(): Bubble {
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+ bubble.setInflateSynchronously(true)
+ bubbleData.notificationEntryUpdated(bubble, true, false)
+
+ val semaphore = Semaphore(0)
+ val callback: BubbleViewInfoTask.Callback =
+ BubbleViewInfoTask.Callback { semaphore.release() }
+ bubble.inflate(
+ callback,
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ positioner,
+ bubbleStackView,
+ null,
+ iconFactory,
+ false
+ )
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubble.isInflated).isTrue()
+ return bubble
+ }
+
+ private class FakeBubbleStackViewManager : BubbleStackViewManager {
+
+ override fun onAllBubblesAnimatedOut() {}
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {}
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {}
+
+ override fun hideCurrentInputMethod() {}
+ }
+
+ private class TestShellExecutor : ShellExecutor {
+
+ override fun execute(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun executeDelayed(r: Runnable, delayMillis: Long) {
+ r.run()
+ }
+
+ override fun removeCallbacks(r: Runnable) {}
+
+ override fun hasCallback(r: Runnable): Boolean = false
+ }
+
+ private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
+ override fun create(): BubbleTaskView {
+ val taskViewTaskController = mock<TaskViewTaskController>()
+ val taskView = TaskView(context, taskViewTaskController)
+ return BubbleTaskView(taskView, shellExecutor)
+ }
+ }
+
+ private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
+
+ override val overflowBubbles: List<Bubble>
+ get() = emptyList()
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {}
+
+ override fun collapseStack() {}
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {}
+
+ override fun removeBubble(key: String, reason: Int) {}
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {}
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {}
+
+ override fun isStackExpanded(): Boolean = false
+
+ override fun isShowingAsBubbleBar(): Boolean = false
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 2ec9e8b..1996367 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -71,6 +71,13 @@
*/
public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(0f, 0f, 0f, 1f);
+
/**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index e7f6f0d..34be9b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -404,10 +404,10 @@
@VisibleForTesting
void onPilferPointers() {
- mCurrentTracker.updateStartLocation();
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
if (!shouldDispatchToAnimator() && mActiveCallback != null) {
+ mCurrentTracker.updateStartLocation();
tryDispatchAppOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 55982dc..d6f7c36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -40,7 +40,6 @@
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -51,6 +50,7 @@
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -65,7 +65,7 @@
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final Interpolator INTERPOLATOR = Interpolators.STANDARD_DECELERATE;
private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP =
new FloatProperty<>("enter-alpha") {
@Override
@@ -285,7 +285,7 @@
ValueAnimator valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
- valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.setInterpolator(INTERPOLATOR);
valueAnimator.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
updatePostCommitEnteringAnimation(progress);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index adc7839..4b31541 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -91,7 +91,8 @@
private final PointF mInitialTouchPos = new PointF();
private final Interpolator mPostAnimationInterpolator = Interpolators.EMPHASIZED;
- private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
+ private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final Matrix mTransformMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
@@ -169,7 +170,7 @@
float yDirection = rawYDelta < 0 ? -1 : 1;
// limit yDelta interpretation to 1/2 of screen height in either direction
float deltaYRatio = Math.min(height / 2f, Math.abs(rawYDelta)) / (height / 2f);
- float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
+ float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio);
// limit y-shift so surface never passes 8dp screen margin
float deltaY = yDirection * interpolatedYRatio * Math.max(0f,
(height - scaledHeight) / 2f - mVerticalMargin);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index cda29c9..a5853d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -395,7 +395,7 @@
public int getTaskViewContentWidth(boolean onLeft) {
int[] paddings = getExpandedViewContainerPadding(onLeft, /* isOverflow = */ false);
int pointerOffset = showBubblesVertically() ? getPointerSize() : 0;
- return mPositionRect.width() - paddings[0] - paddings[2] - pointerOffset;
+ return mScreenRect.width() - paddings[0] - paddings[2] - pointerOffset;
}
/** Gets the y position of the expanded view if it was top-aligned. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index 81d1399..7c28099 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
@@ -88,7 +89,8 @@
mShellExecutor = shellExecutor;
mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker;
mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
mCompatUIHintsState = compatUIHintsState;
mOnButtonClicked = onButtonClicked;
mDisappearTimeSupplier = disappearTimeSupplier;
@@ -134,7 +136,8 @@
public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
@NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
@@ -227,12 +230,21 @@
return SystemClock.uptimeMillis() + hideDelay;
}
- private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
- final Intent intent = taskInfo.baseIntent;
- return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
- && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
- || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
- && !taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+ private boolean shouldShowUserAspectRatioSettingsButton(@NonNull AppCompatTaskInfo taskInfo,
+ @NonNull Intent intent) {
+ final Rect stableBounds = getTaskStableBounds();
+ final int letterboxHeight = taskInfo.topActivityLetterboxHeight;
+ final int letterboxWidth = taskInfo.topActivityLetterboxWidth;
+ // App is not visibly letterboxed if it covers status bar/bottom insets or matches the
+ // stable bounds, so don't show the button
+ if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) {
+ return false;
+ }
+
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled)
+ && !taskInfo.isSystemFullscreenOverrideEnabled
&& Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
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 8eecf1c..458ea05 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
@@ -74,13 +74,18 @@
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ @ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
} else {
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
- pipDisplayLayoutState));
+ pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
+ mainExecutor));
}
}
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 186cb61..e73a850 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
@@ -18,14 +18,31 @@
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -37,32 +54,54 @@
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
- DisplayController.OnDisplaysChangedListener {
+ DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
private Context mContext;
private ShellController mShellController;
private DisplayController mDisplayController;
private DisplayInsetsController mDisplayInsetsController;
+ private PipBoundsState mPipBoundsState;
+ private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipDisplayLayoutState mPipDisplayLayoutState;
+ private PipScheduler mPipScheduler;
+ private ShellExecutor mMainExecutor;
private PipController(Context context,
ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipScheduler = pipScheduler;
+ mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void onInit() {
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
@@ -80,6 +119,10 @@
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
+
+ // Allow other outside processes to bind to PiP controller using the key below.
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
}
/**
@@ -90,16 +133,24 @@
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Device doesn't support Pip feature", TAG);
return null;
}
return new PipController(context, shellInit, shellController, displayController,
- displayInsetsController, pipDisplayLayoutState);
+ displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
+ pipScheduler, mainExecutor);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
+ }
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
@@ -130,4 +181,86 @@
private void onDisplayChanged(DisplayLayout layout) {
mPipDisplayLayoutState.setDisplayLayout(layout);
}
+
+ private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, Rect hotseatKeepClearArea) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "getSwipePipToHomeBounds: %s", componentName);
+ mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
+ mPipBoundsAlgorithm);
+ return mPipBoundsAlgorithm.getEntryDestinationBounds();
+ }
+
+ private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onSwipePipToHomeAnimationStart: %s", componentName);
+ mPipScheduler.setInSwipePipToHomeTransition(true);
+ // TODO: cache the overlay if provided for reparenting later.
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
+ private PipController mController;
+
+ IPipImpl(PipController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams, int launcherRotation,
+ Rect keepClearArea) {
+ Rect[] result = new Rect[1];
+ executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
+ (controller) -> {
+ result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, keepClearArea);
+ }, true /* blocking */);
+ return result[0];
+ }
+
+ @Override
+ public void stopSwipePipToHome(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ if (overlay != null) {
+ overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
+ }
+ executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
+ (controller) -> controller.onSwipePipToHomeAnimationStart(
+ taskId, componentName, destinationBounds, overlay, appBounds));
+ }
+
+ @Override
+ public void abortSwipePipToHome(int taskId, ComponentName componentName) {}
+
+ @Override
+ public void setShelfHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {}
+
+ @Override
+ public void setPipAnimationListener(IPipAnimationListener listener) {
+ // TODO: set a proper animation listener to update the Launcher state as needed.
+ }
+
+ @Override
+ public void setPipAnimationTypeToAlpha() {}
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 57b73b3..895c793 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -63,6 +63,9 @@
@Nullable
private SurfaceControl mPinnedTaskLeash;
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -168,6 +171,14 @@
mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
}
+ void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
+ mInSwipePipToHomeTransition = true;
+ }
+
+ boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
void onExitPip() {
mPipTaskToken = null;
mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index fbf4d13..dfb0475 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -152,6 +152,12 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition == mEnterTransition) {
mEnterTransition = null;
+ if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If this is the second transition as a part of swipe PiP to home cuj,
+ // handle this transition as a special case with no-op animation.
+ return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
if (isLegacyEnter(info)) {
// If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause),
// then we should run an ALPHA type (cross-fade) animation.
@@ -207,6 +213,25 @@
return true;
}
+ private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipScheduler.setInSwipePipToHomeTransition(false);
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index 47a116b..2ef425c 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.VIBRATE"/>
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
<application android:debuggable="true" android:largeHeap="true">
<uses-library android:name="android.test.mock" />
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 9fe2cb1..81ba4b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -113,8 +113,22 @@
mExecutor = new TestShellExecutor();
mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ final int displayWidth = 1000;
+ final int displayHeight = 1200;
+ displayInfo.logicalWidth = displayWidth;
+ displayInfo.logicalHeight = displayHeight;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+ InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, displayWidth, displayHeight));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, displayHeight - 200, displayWidth, displayHeight);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
- mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mSyncTransactionQueue, mTaskListener, displayLayout, new CompatUIHintsState(),
mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
mUserAspectRatioButtonShownChecker, s -> {});
spyOn(mWindowManager);
@@ -253,6 +267,31 @@
}
@Test
+ public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+
+ final Rect stableBounds = mWindowManager.getTaskStableBounds();
+ final int stableHeight = stableBounds.height();
+
+ // Letterboxed activity bounds equal to stable bounds, layout shouldn't be inflated
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableHeight;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width();
+
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Letterboxed activity bounds smaller than stable bounds, layout should be inflated
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableHeight - 100;
+
+ clearInvocations(mWindowManager);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ }
+
+ @Test
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index b667daf..30e7a62 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -137,9 +137,10 @@
return palette;
}
- SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK;
- color = paint->getColorFilter()->filterColor(color);
- return paletteForColorHSV(color);
+ SkColor4f color = palette == BitmapPalette::Light ? SkColors::kWhite : SkColors::kBlack;
+ sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
+ color = paint->getColorFilter()->filterColor4f(color, srgb.get(), srgb.get());
+ return paletteForColorHSV(color.toSkColor());
}
bool transformPaint(ColorTransform transform, SkPaint* paint) {
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index fd27641..28d85bd 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -218,7 +218,7 @@
}
// Perform clipping
- if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) {
+ if (props.getClipDamageToBounds()) {
if (!frame->pendingDirty.intersect(SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
frame->pendingDirty.setEmpty();
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9c7f7cc..1d03301 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -1067,6 +1067,7 @@
if (dirty->isEmpty()) {
dirty->setIWH(frame.width(), frame.height());
+ return *dirty;
}
// At this point dirty is the area of the window to update. However,
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index f264b16..32dba5f 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,7 +105,6 @@
*/
private final HashMap<String, AidGroup> mDynamicAidGroups;
- private final ArrayList<String> mPollingLoopFilters;
private final Map<String, Boolean> mAutoTransact;
@@ -181,7 +180,6 @@
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
- this.mPollingLoopFilters = new ArrayList<String>();
this.mAutoTransact = new HashMap<String, Boolean>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
@@ -302,7 +300,6 @@
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
- mPollingLoopFilters = new ArrayList<String>();
mAutoTransact = new HashMap<String, Boolean>();
mOnHost = onHost;
@@ -393,7 +390,6 @@
String plf =
a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
.toUpperCase(Locale.ROOT);
- mPollingLoopFilters.add(plf);
boolean autoTransact = a.getBoolean(
com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
false);
@@ -461,7 +457,7 @@
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
@NonNull
public List<String> getPollingLoopFilters() {
- return mPollingLoopFilters;
+ return new ArrayList<>(mAutoTransact.keySet());
}
/**
@@ -672,12 +668,15 @@
/**
* Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
- * delivered to {@link HostApduService#processPollingFrames(List)}.
- * @param pollingLoopFilter this polling loop filter to add.
+ * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this or
+ * {@link ApduServiceInfo#addPollingLoopFilterToAutoTransact(String)} multiple times will
+ * cause the value to be overwritten each time.
+ * @param pollingLoopFilter the polling loop filter to add, must be a valide hexadecimal string
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
- mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), false);
+
}
/**
@@ -685,13 +684,14 @@
* device to exit observe mode, just as if
* {@link android.nfc.NfcAdapter#setTransactionAllowed(boolean)} had been called with true,
* allowing transactions to proceed. The matching frame will also be delivered to
- * {@link HostApduService#processPollingFrames(List)}.
+ * {@link HostApduService#processPollingFrames(List)}. Adding a key with this or
+ * {@link ApduServiceInfo#addPollingLoopFilter(String)} multiple times will
+ * cause the value to be overwritten each time.
*
- * @param pollingLoopFilter this polling loop filter to add.
+ * @param pollingLoopFilter the polling loop filter to add, must be a valide hexadecimal string
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
- mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
}
@@ -702,7 +702,7 @@
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
- mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
}
/**
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index b5cf011..ce8fb65 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -100,13 +100,15 @@
public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
public static final int FAILURE_REASON_APP_CRASH = 3;
public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+ public static final int FAILURE_REASON_BOOT_LOOP = 5;
@IntDef(prefix = { "FAILURE_REASON_" }, value = {
FAILURE_REASON_UNKNOWN,
FAILURE_REASON_NATIVE_CRASH,
FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
FAILURE_REASON_APP_CRASH,
- FAILURE_REASON_APP_NOT_RESPONDING
+ FAILURE_REASON_APP_NOT_RESPONDING,
+ FAILURE_REASON_BOOT_LOOP
})
@Retention(RetentionPolicy.SOURCE)
public @interface FailureReasons {}
@@ -542,7 +544,7 @@
mNumberOfNativeCrashPollsRemaining--;
// Check if native watchdog reported a crash
if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- // We rollback everything available when crash is unattributable
+ // We rollback all available low impact rollbacks when crash is unattributable
onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
// we stop polling after an attempt to execute rollback, regardless of whether the
// attempt succeeds or not
@@ -572,6 +574,7 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
public @interface PackageHealthObserverImpact {
/** No action to take. */
@@ -582,6 +585,7 @@
int USER_IMPACT_LEVEL_30 = 30;
int USER_IMPACT_LEVEL_50 = 50;
int USER_IMPACT_LEVEL_70 = 70;
+ int USER_IMPACT_LEVEL_90 = 90;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
int USER_IMPACT_LEVEL_100 = 100;
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index dd74a2a..5fb47dd 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -28,6 +28,7 @@
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.crashrecovery.flags.Flags;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -45,7 +46,6 @@
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.SystemConfig;
import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import com.android.server.pm.ApexManager;
@@ -57,6 +57,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -84,7 +85,8 @@
// True if needing to roll back only rebootless apexes when native crash happens
private boolean mTwoPhaseRollbackEnabled;
- RollbackPackageHealthObserver(Context context) {
+ @VisibleForTesting
+ RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
mContext = context;
HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
handlerThread.start();
@@ -94,7 +96,7 @@
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
- mApexManager = ApexManager.getInstance();
+ mApexManager = apexManager;
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
// Load the value from the file if system server has crashed and restarted
@@ -107,24 +109,46 @@
}
}
+ RollbackPackageHealthObserver(Context context) {
+ this(context, ApexManager.getInstance());
+ }
+
@Override
public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
@FailureReasons int failureReason, int mitigationCount) {
- boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty();
int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (!lowImpactRollbacks.isEmpty()) {
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ // For native crashes, we will directly roll back any available rollbacks at low
+ // impact level
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
+ // Rollback is available for crashing low impact package
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else {
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
+ }
+ } else {
+ boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+ .getAvailableRollbacks().isEmpty();
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
- && anyRollbackAvailable) {
- // For native crashes, we will directly roll back any available rollbacks
- // Note: For non-native crashes the rollback-all step has higher impact
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getAvailableRollback(failedPackage) != null) {
- // Rollback is available, we may get a callback into #execute
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (anyRollbackAvailable) {
- // If any rollbacks are available, we will commit them
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
+ && anyRollbackAvailable) {
+ // For native crashes, we will directly roll back any available rollbacks
+ // Note: For non-native crashes the rollback-all step has higher impact
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getAvailableRollback(failedPackage) != null) {
+ // Rollback is available, we may get a callback into #execute
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (anyRollbackAvailable) {
+ // If any rollbacks are available, we will commit them
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
}
return impact;
@@ -133,16 +157,34 @@
@Override
public boolean execute(@Nullable VersionedPackage failedPackage,
@FailureReasons int rollbackReason, int mitigationCount) {
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAll(rollbackReason));
- return true;
- }
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ return true;
+ }
- RollbackInfo rollback = getAvailableRollback(failedPackage);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else if (!lowImpactRollbacks.isEmpty()) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ }
} else {
- mHandler.post(() -> rollbackAll(rollbackReason));
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ return true;
+ }
+
+ RollbackInfo rollback = getAvailableRollback(failedPackage);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ }
}
// Assume rollbacks executed successfully
@@ -150,6 +192,31 @@
}
@Override
+ public int onBootLoop(int mitigationCount) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (!availableRollbacks.isEmpty()) {
+ impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
+ }
+ }
+ return impact;
+ }
+
+ @Override
+ public boolean executeBootLoopMitigation(int mitigationCount) {
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+
+ triggerLeastImpactLevelRollback(availableRollbacks,
+ PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
+ return true;
+ }
+ return false;
+ }
+
+
+ @Override
public String getName() {
return NAME;
}
@@ -161,13 +228,16 @@
@Override
public boolean mayObservePackage(String packageName) {
- if (mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty()) {
+ if (getAvailableRollbacks().isEmpty()) {
return false;
}
return isPersistentSystemApp(packageName);
}
+ private List<RollbackInfo> getAvailableRollbacks() {
+ return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
+ }
+
private boolean isPersistentSystemApp(@NonNull String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
@@ -272,6 +342,40 @@
return null;
}
+ @AnyThread
+ private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
+ List<RollbackInfo> availableRollbacks) {
+ if (failedPackage == null) {
+ return null;
+ }
+
+ for (RollbackInfo rollback : availableRollbacks) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
+ return rollback;
+ }
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Returns {@code true} if staged session associated with {@code rollbackId} was marked
* as handled, {@code false} if already handled.
@@ -396,12 +500,6 @@
@FailureReasons int rollbackReason) {
assertInWorkerThread();
- if (isAutomaticRollbackDenied(SystemConfig.getInstance(), failedPackage)) {
- Slog.d(TAG, "Automatic rollback not allowed for package "
- + failedPackage.getPackageName());
- return;
- }
-
final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
final String failedPackageToLog;
@@ -465,17 +563,6 @@
}
/**
- * Returns true if this package is not eligible for automatic rollback.
- */
- @VisibleForTesting
- @AnyThread
- public static boolean isAutomaticRollbackDenied(SystemConfig systemConfig,
- VersionedPackage versionedPackage) {
- return systemConfig.getAutomaticRollbackDenylistedPackages()
- .contains(versionedPackage.getPackageName());
- }
-
- /**
* Two-phase rollback:
* 1. roll back rebootless apexes first
* 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
@@ -495,14 +582,62 @@
boolean found = false;
for (RollbackInfo rollback : rollbacks) {
if (isRebootlessApex(rollback)) {
- VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
found = true;
}
}
return found;
}
+ /**
+ * Rollback the package that has minimum rollback impact level.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
+
+ if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
+ // Rollback one package at a time. If that doesn't resolve the issue, rollback
+ // next with same impact level.
+ mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
+ }
+ }
+
+ /**
+ * sort the available high impact rollbacks by first package name to have a deterministic order.
+ * Apply the first available rollback.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ @WorkerThread
+ private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+ List<RollbackInfo> highImpactRollbacks =
+ getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+
+ // sort rollbacks based on package name of the first package. This is to have a
+ // deterministic order of rollbacks.
+ List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
+ Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
+ VersionedPackage firstRollback =
+ sortedHighImpactRollbacks
+ .get(0)
+ .getPackages()
+ .get(0)
+ .getVersionRolledBackFrom();
+ rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
+ }
+
@WorkerThread
private void rollbackAll(@FailureReasons int rollbackReason) {
assertInWorkerThread();
@@ -522,8 +657,77 @@
}
for (RollbackInfo rollback : rollbacks) {
- VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, rollbackReason);
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
}
}
+
+ /**
+ * Rollback all available low impact rollbacks
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollbacks
+ */
+ @WorkerThread
+ private void rollbackAllLowImpact(
+ List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (useTwoPhaseRollback(lowImpactRollbacks)) {
+ return;
+ }
+
+ Slog.i(TAG, "Rolling back all available low impact rollbacks");
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
+ }
+ }
+
+ private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
+ List<RollbackInfo> availableRollbacks, int impactLevel) {
+ return availableRollbacks.stream()
+ .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
+ .toList();
+ }
+
+ private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ return availableRollbacks.stream()
+ .mapToInt(RollbackInfo::getRollbackImpactLevel)
+ .min()
+ .orElse(-1);
+ }
+
+ private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ int minImpact = getMinRollbackImpactLevel(availableRollbacks);
+ switch (minImpact) {
+ case PackageManager.ROLLBACK_USER_IMPACT_LOW:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ break;
+ case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+ break;
+ default:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ return impact;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 898c543..519c0ed 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -18,6 +18,7 @@
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
@@ -258,6 +259,8 @@
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+ case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
default:
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
index 3fbff37..6f70cfb 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
@@ -19,6 +19,7 @@
import android.content.Intent
import android.credentials.selection.BaseDialogResult
import android.credentials.selection.UserSelectionDialogResult
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.Request
import kotlinx.coroutines.flow.StateFlow
@@ -54,4 +55,20 @@
* @throws [IllegalStateException] if [requests] is not [Request.Get].
*/
fun sendResult(result: UserSelectionDialogResult)
+
+ /**
+ * Sends a response to the system service with a selected [EntryInfo].
+ *
+ * @return if the current [Request.Get] flow can be ended peacefully.
+ * if not, App has to keep reacting to the further update from [requests] until [Request.Cancel]
+ * or [Request.Close] is received.
+ *
+ * @throws [IllegalStateException] if [requests] is not [Request.Get].
+ */
+ fun sendEntrySelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ isAutoSelected: Boolean = false,
+ ): Boolean
}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index ec1f052..7f59ba3 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -16,16 +16,24 @@
package com.android.credentialmanager.client.impl
+import android.app.Activity
import android.content.Context
import android.content.Intent
import android.credentials.selection.BaseDialogResult
+import android.credentials.selection.BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED
+import android.credentials.selection.Constants
+import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
import android.os.Bundle
+import android.os.IBinder
+import android.os.ResultReceiver
import android.util.Log
import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.parse
import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.EntryInfo
+
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -69,4 +77,58 @@
)
}
}
+
+ override fun sendEntrySelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int?,
+ resultData: Intent?,
+ isAutoSelected: Boolean,
+ ): Boolean {
+ Log.d(TAG, "sendEntrySelectionResult, resultCode: $resultCode, resultData: $resultData," +
+ " entryInfo: $entryInfo")
+ val currentRequest = requests.value
+ check(currentRequest is Request.Get) { "current request is not get." }
+ if (resultCode == Activity.RESULT_CANCELED) {
+ if (isAutoSelected) {
+ currentRequest.sendCancellationCode(RESULT_CODE_DIALOG_USER_CANCELED)
+ }
+ return isAutoSelected
+ }
+ val userSelectionDialogResult = UserSelectionDialogResult(
+ currentRequest.token,
+ entryInfo.providerId,
+ entryInfo.entryKey,
+ entryInfo.entrySubkey,
+ if (resultCode != null) ProviderPendingIntentResponse(
+ resultCode,
+ resultData
+ ) else null
+ )
+ sendResult(userSelectionDialogResult)
+ return entryInfo.shouldTerminateUiUponSuccessfulProviderResult
+ }
+
+ private fun Request.Get.sendCancellationCode(cancelCode: Int) {
+ sendCancellationCode(
+ cancelCode = cancelCode,
+ requestToken = token,
+ resultReceiver = resultReceiver,
+ finalResponseReceiver = finalResponseReceiver
+ )
+ }
+
+ private fun sendCancellationCode(
+ cancelCode: Int,
+ requestToken: IBinder?,
+ resultReceiver: ResultReceiver?,
+ finalResponseReceiver: ResultReceiver?
+ ) {
+ if (requestToken != null && resultReceiver != null) {
+ val resultData = Bundle().apply {
+ putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER, finalResponseReceiver)
+ }
+ BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
+ resultReceiver.send(cancelCode, resultData)
+ }
+ }
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 9242141..786c441 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -54,3 +54,9 @@
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
+
+val Intent.finalResponseReceiver: ResultReceiver?
+ get() = this.getParcelableExtra(
+ Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver::class.java
+ )
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index f1f1f7c..1683cc4 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import com.android.credentialmanager.ktx.getCredentialProviderDataList
import com.android.credentialmanager.ktx.requestInfo
+import com.android.credentialmanager.ktx.finalResponseReceiver
import com.android.credentialmanager.ktx.resultReceiver
import com.android.credentialmanager.ktx.toProviderList
import com.android.credentialmanager.model.Request
@@ -28,6 +29,7 @@
return Request.Get(
token = requestInfo?.token,
resultReceiver = resultReceiver,
+ finalResponseReceiver = finalResponseReceiver,
providerInfos = getCredentialProviderDataList.toProviderList(context)
)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index 7636462..4a1c260 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -49,6 +49,7 @@
data class Get(
override val token: IBinder?,
val resultReceiver: ResultReceiver?,
+ val finalResponseReceiver: ResultReceiver?,
val providerInfos: List<ProviderInfo>,
) : Request(token)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 3097387..6100623 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -50,7 +50,6 @@
class CredentialManagerRepo(
private val context: Context,
intent: Intent,
- userConfigRepo: UserConfigRepo,
isNewActivity: Boolean,
) {
val requestInfo: RequestInfo?
@@ -124,7 +123,6 @@
initialUiState = when (requestInfo?.type) {
RequestInfo.TYPE_CREATE -> {
- val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
val requestDisplayInfoUiState =
@@ -137,8 +135,6 @@
defaultProviderIdsSetByUser =
requestDisplayInfoUiState.userSetDefaultProviderIds,
requestDisplayInfo = requestDisplayInfoUiState,
- isOnPasskeyIntroStateAlready = false,
- isPasskeyFirstUse = isPasskeyFirstUse,
)!!
val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState)
UiState(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 4771237..ec0da09 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -61,9 +61,7 @@
if (isCancellationRequest && !shouldShowCancellationUi) {
return
}
- val userConfigRepo = UserConfigRepo(this)
- val credManRepo = CredentialManagerRepo(
- this, intent, userConfigRepo, isNewActivity = true)
+ val credManRepo = CredentialManagerRepo(this, intent, isNewActivity = true)
val backPressedCallback = object : OnBackPressedCallback(
true // default to enabled
@@ -78,10 +76,7 @@
setContent {
PlatformTheme {
- CredentialManagerBottomSheet(
- credManRepo,
- userConfigRepo
- )
+ CredentialManagerBottomSheet(credManRepo)
}
}
} catch (e: Exception) {
@@ -103,9 +98,7 @@
return
}
} else {
- val userConfigRepo = UserConfigRepo(this)
- val credManRepo = CredentialManagerRepo(
- this, intent, userConfigRepo, isNewActivity = false)
+ val credManRepo = CredentialManagerRepo(this, intent, isNewActivity = false)
viewModel.onNewCredentialManagerRepo(credManRepo)
}
} catch (e: Exception) {
@@ -147,10 +140,9 @@
@Composable
private fun CredentialManagerBottomSheet(
credManRepo: CredentialManagerRepo,
- userConfigRepo: UserConfigRepo,
) {
val viewModel: CredentialSelectorViewModel = viewModel {
- CredentialSelectorViewModel(credManRepo, userConfigRepo)
+ CredentialSelectorViewModel(credManRepo)
}
val launcher = rememberLauncherForActivityResult(
StartBalIntentSenderForResultContract()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index f4da1e6..1f2fa20 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -34,7 +34,6 @@
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.ActiveEntry
-import com.android.credentialmanager.createflow.isFlowAutoSelectable
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState
@@ -63,7 +62,6 @@
class CredentialSelectorViewModel(
private var credManRepo: CredentialManagerRepo,
- private val userConfigRepo: UserConfigRepo,
) : ViewModel() {
var uiState by mutableStateOf(credManRepo.initState())
private set
@@ -266,42 +264,6 @@
/**************************************************************************/
/***** Create Flow Callbacks *****/
/**************************************************************************/
- fun createFlowOnConfirmIntro() {
- userConfigRepo.setIsPasskeyFirstUse(false)
- val prevUiState = uiState.createCredentialUiState
- if (prevUiState == null) {
- Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state")
- onInternalError()
- return
- }
- val newScreenState = CreateFlowUtils.toCreateScreenState(
- createOptionSize = prevUiState.sortedCreateOptionsPairs.size,
- isOnPasskeyIntroStateAlready = true,
- requestDisplayInfo = prevUiState.requestDisplayInfo,
- remoteEntry = prevUiState.remoteEntry,
- isPasskeyFirstUse = true,
- )
- if (newScreenState == null) {
- Log.d(Constants.LOG_TAG, "Unexpected: couldn't resolve new screen state")
- onInternalError()
- return
- }
- val newCreateCredentialUiState = prevUiState.copy(
- currentScreenState = newScreenState,
- )
- val isFlowAutoSelectable = isFlowAutoSelectable(newCreateCredentialUiState)
- uiState = uiState.copy(
- createCredentialUiState = newCreateCredentialUiState,
- isAutoSelectFlow = isFlowAutoSelectable,
- providerActivityState =
- if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH
- else ProviderActivityState.NOT_APPLICABLE,
- selectedEntry =
- if (isFlowAutoSelectable) newCreateCredentialUiState.activeEntry?.activeEntryInfo
- else null,
- )
- }
-
fun createFlowOnMoreOptionsSelectedOnCreationSelection() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -318,14 +280,6 @@
)
}
- fun createFlowOnBackPasskeyIntroButtonSelected() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.PASSKEY_INTRO,
- )
- )
- }
-
fun createFlowOnEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -348,14 +302,6 @@
uiState = uiState.copy(dialogState = DialogState.CANCELED_FOR_SETTINGS)
}
- fun createFlowOnLearnMore() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO,
- )
- )
- }
-
fun createFlowOnUseOnceSelected() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 997c45e..5830b9f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -342,8 +342,6 @@
defaultProviderIdPreferredByApp: String?,
defaultProviderIdsSetByUser: Set<String>,
requestDisplayInfo: RequestDisplayInfo,
- isOnPasskeyIntroStateAlready: Boolean,
- isPasskeyFirstUse: Boolean,
): CreateCredentialUiState? {
var remoteEntry: RemoteInfo? = null
var remoteEntryProvider: EnabledProviderInfo? = null
@@ -392,11 +390,8 @@
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
val initialScreenState = toCreateScreenState(
createOptionSize = createOptionsPairs.size,
- isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
- requestDisplayInfo = requestDisplayInfo,
remoteEntry = remoteEntry,
- isPasskeyFirstUse = isPasskeyFirstUse
- ) ?: return null
+ )
val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
compareByDescending { it.first.lastUsedTime }
)
@@ -419,15 +414,9 @@
fun toCreateScreenState(
createOptionSize: Int,
- isOnPasskeyIntroStateAlready: Boolean,
- requestDisplayInfo: RequestDisplayInfo,
remoteEntry: RemoteInfo?,
- isPasskeyFirstUse: Boolean,
- ): CreateScreenState? {
- return if (isPasskeyFirstUse && requestDisplayInfo.type == CredentialType.PASSKEY &&
- !isOnPasskeyIntroStateAlready) {
- CreateScreenState.PASSKEY_INTRO
- } else if (createOptionSize == 0 && remoteEntry != null) {
+ ): CreateScreenState {
+ return if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else {
CreateScreenState.CREATION_OPTION_SELECTION
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
deleted file mode 100644
index bfcca49..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 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.credentialmanager
-
-import android.content.Context
-import android.content.SharedPreferences
-
-class UserConfigRepo(context: Context) {
- val sharedPreferences: SharedPreferences = context.getSharedPreferences(
- context.packageName, Context.MODE_PRIVATE)
-
- fun setIsPasskeyFirstUse(
- isFirstUse: Boolean
- ) {
- sharedPreferences.edit().apply {
- putBoolean(IS_PASSKEY_FIRST_USE, isFirstUse)
- apply()
- }
- }
-
- fun getIsPasskeyFirstUse(): Boolean {
- return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
- }
-
- companion object {
- // This first use value only applies to passkeys, not related with if generally
- // credential manager is first use or not
- const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index f261d1f..d24adb5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -20,8 +20,6 @@
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -36,12 +34,9 @@
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -63,10 +58,8 @@
import com.android.credentialmanager.common.ui.HeadlineIcon
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ModalBottomSheet
-import com.android.credentialmanager.common.ui.MoreAboutPasskeySectionHeader
import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
import com.android.credentialmanager.common.ui.SheetContainerCard
-import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.logging.CreateCredentialEvent
import com.android.credentialmanager.model.creation.CreateOptionInfo
@@ -87,11 +80,6 @@
when (viewModel.uiState.providerActivityState) {
ProviderActivityState.NOT_APPLICABLE -> {
when (createCredentialUiState.currentScreenState) {
- CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard(
- onConfirm = viewModel::createFlowOnConfirmIntro,
- onLearnMore = viewModel::createFlowOnLearnMore,
- onLog = { viewModel.logUiEvent(it) },
- )
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -144,11 +132,6 @@
onConfirm = viewModel::createFlowOnConfirmEntrySelected,
onLog = { viewModel.logUiEvent(it) },
)
- CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
- onBackPasskeyIntroButtonSelected =
- viewModel::createFlowOnBackPasskeyIntroButtonSelected,
- onLog = { viewModel.logUiEvent(it) },
- )
}
}
ProviderActivityState.READY_TO_LAUNCH -> {
@@ -188,78 +171,6 @@
}
@Composable
-fun PasskeyIntroCard(
- onConfirm: () -> Unit,
- onLearnMore: () -> Unit,
- onLog: @Composable (UiEventEnum) -> Unit,
-) {
- SheetContainerCard {
- item {
- val onboardingImageResource = remember {
- mutableStateOf(R.drawable.ic_passkeys_onboarding)
- }
- if (isSystemInDarkTheme()) {
- onboardingImageResource.value = R.drawable.ic_passkeys_onboarding_dark
- } else {
- onboardingImageResource.value = R.drawable.ic_passkeys_onboarding
- }
- Row(
- modifier = Modifier.wrapContentHeight().fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- ) {
- Image(
- painter = painterResource(onboardingImageResource.value),
- contentDescription = null,
- modifier = Modifier.size(316.dp, 168.dp)
- )
- }
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item { HeadlineText(text = stringResource(R.string.passkey_creation_intro_title)) }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- PasskeyBenefitRow(
- leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_password),
- text = stringResource(R.string.passkey_creation_intro_body_password),
- )
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- PasskeyBenefitRow(
- leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
- text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
- )
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- PasskeyBenefitRow(
- leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_device),
- text = stringResource(R.string.passkey_creation_intro_body_device),
- )
- }
- item { Divider(thickness = 24.dp, color = Color.Transparent) }
-
- item {
- CtaButtonRow(
- leftButton = {
- ActionButton(
- stringResource(R.string.string_learn_more),
- onClick = onLearnMore
- )
- },
- rightButton = {
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- },
- )
- }
- }
- onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO)
-}
-
-@Composable
fun MoreOptionsSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
enabledProviderList: List<EnabledProviderInfo>,
@@ -522,59 +433,6 @@
}
@Composable
-fun MoreAboutPasskeysIntroCard(
- onBackPasskeyIntroButtonSelected: () -> Unit,
- onLog: @Composable (UiEventEnum) -> Unit,
-) {
- SheetContainerCard(
- topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(R.string.more_about_passkeys_title),
- onNavigationIconClicked = onBackPasskeyIntroButtonSelected,
- bottomPadding = 0.dp,
- )
- },
- ) {
- item {
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.passwordless_technology_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.passwordless_technology_detail))
- }
- }
- item {
- Divider(thickness = 8.dp, color = Color.Transparent)
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.public_key_cryptography_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail))
- }
- }
- item {
- Divider(thickness = 8.dp, color = Color.Transparent)
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.improved_account_security_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.improved_account_security_detail))
- }
- }
- item {
- Divider(thickness = 8.dp, color = Color.Transparent)
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.seamless_transition_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
- }
- }
- }
- onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO)
-}
-
-@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
entryInfo: EntryInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 8b0ba87..617a981 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -37,10 +37,6 @@
uiState: CreateCredentialUiState
): Boolean {
return uiState.requestDisplayInfo.isAutoSelectRequest &&
- // Even if the flow is auto selectable, still allow passkey intro screen to show once if
- // applicable.
- uiState.currentScreenState != CreateScreenState.PASSKEY_INTRO &&
- uiState.currentScreenState != CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO &&
uiState.sortedCreateOptionsPairs.size == 1 &&
uiState.activeEntry?.activeEntryInfo?.let {
it is CreateOptionInfo && it.allowAutoSelect
@@ -98,8 +94,6 @@
/** The name of the current screen. */
enum class CreateScreenState {
- PASSKEY_INTRO,
- MORE_ABOUT_PASSKEYS_INTRO,
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 4e9174e..9480e64 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -27,11 +27,11 @@
<!-- Title of a screen prompting if the user would like to sign in with provider
[CHAR LIMIT=80] -->
<string name="use_password_title">Use password?</string>
- <!-- Content description for the dismiss button of a screen. [CHAR LIMIT=NONE] -->
+ <!-- Text on this dismiss button of a screen. [CHAR LIMIT=NONE] -->
<string name="dialog_dismiss_button">Dismiss</string>
- <!-- Content description for the continue button of a screen. [CHAR LIMIT=NONE] -->
+ <!-- Text on the continue button of a screen. [CHAR LIMIT=NONE] -->
<string name="dialog_continue_button">Continue</string>
- <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] -->
+ <!-- Text on the sign in options button of a screen. [CHAR LIMIT=NONE] -->
<string name="dialog_sign_in_options_button">Sign-in Options</string>
<!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] -->
<string name="sign_in_options_title">Sign-in Options</string>
@@ -41,4 +41,11 @@
<string name="choose_passkey_title">Choose passkey</string>
<!-- Title for multiple credentials screen with only passwords. [CHAR LIMIT=NONE] -->
<string name="choose_password_title">Choose password</string>
+ <!-- Text on the sign in on phone button [CHAR LIMIT=NONE] -->
+ <string name="sign_in_on_phone_button">Sign in on phone</string>
+ <!-- Text on the locked provider button when unlocked[CHAR LIMIT=NONE] -->
+ <string name="locked_credential_entry_label_subtext_no_sign_in">No sign-in info</string>
+ <!-- Text on the locked provider button when locked[CHAR LIMIT=NONE] -->
+ <string name="locked_credential_entry_label_subtext_tap_to_unlock">Tap to unlock</string>
+
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0df40d7..283dc7d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,7 +25,6 @@
import com.android.credentialmanager.ui.WearApp
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import dagger.hilt.android.AndroidEntryPoint
-import kotlin.system.exitProcess
@AndroidEntryPoint(ComponentActivity::class)
class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
@@ -40,7 +39,7 @@
MaterialTheme {
WearApp(
viewModel = viewModel,
- onCloseApp = { exitProcess(0) },
+ onCloseApp = { finish() },
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 2fc98e2..bfe80c4 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -19,11 +19,15 @@
import android.content.Intent
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.credentialmanager.CredentialSelectorUiState.Get
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.mappers.toGet
+import android.util.Log
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -35,10 +39,20 @@
@HiltViewModel
class CredentialSelectorViewModel @Inject constructor(
private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
- private val isPrimaryScreen = MutableStateFlow(false)
- val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
- .combine(isPrimaryScreen) { request, isPrimary ->
+) : FlowEngine, ViewModel() {
+ private val isPrimaryScreen = MutableStateFlow(true)
+ private val shouldClose = MutableStateFlow(false)
+ val uiState: StateFlow<CredentialSelectorUiState> =
+ combine(
+ credentialManagerClient.requests,
+ isPrimaryScreen,
+ shouldClose
+ ) { request, isPrimary, shouldClose ->
+ if (shouldClose) {
+ Log.d(TAG, "Request finished, closing ")
+ return@combine CredentialSelectorUiState.Close
+ }
+
when (request) {
null -> CredentialSelectorUiState.Idle
is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
@@ -56,6 +70,41 @@
fun updateRequest(intent: Intent) {
credentialManagerClient.updateRequest(intent = intent)
}
+
+ override fun back() {
+ Log.d(TAG, "OnBackPressed")
+ when (uiState.value) {
+ is Get.MultipleEntry -> isPrimaryScreen.value = true
+ else -> {
+ shouldClose.value = true
+ // TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+ }
+ }
+ }
+
+ override fun cancel() {
+ shouldClose.value = true
+ // TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+ }
+
+ override fun openSecondaryScreen() {
+ isPrimaryScreen.value = false
+ }
+
+ override fun sendSelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int?,
+ resultData: Intent?,
+ isAutoSelected: Boolean,
+ ) {
+ val result = credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = entryInfo,
+ resultCode = resultCode,
+ resultData = resultData,
+ isAutoSelected = isAutoSelected
+ )
+ shouldClose.value = result
+ }
}
sealed class CredentialSelectorUiState {
@@ -66,6 +115,7 @@
data class MultipleEntry(
val accounts: List<PerUserNameEntries>,
val actionEntryList: List<ActionEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
) : Get() {
data class PerUserNameEntries(
val userName: String,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
new file mode 100644
index 0000000..e421644
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.Intent
+import com.android.credentialmanager.model.EntryInfo
+
+/** Engine of the credential selecting flow. */
+interface FlowEngine {
+ /** Back from previous stage. */
+ fun back()
+ /** Cancels the selection flow. */
+ fun cancel()
+ /** Opens secondary screen. */
+ fun openSecondaryScreen()
+ /**
+ * Sends [entryInfo] as long as result after launching [EntryInfo.pendingIntent] with
+ * [EntryInfo.fillInIntent].
+ *
+ * @param entryInfo: selected entry.
+ * @param resultCode: result code received after launch.
+ * @param resultData: data received after launch
+ * @param isAutoSelected: whether the entry is auto selected or by user.
+ */
+ fun sendSelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ isAutoSelected: Boolean = false,
+ )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index f7158e8..332b816 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -18,8 +18,11 @@
package com.android.credentialmanager.ui
+import android.util.Log
+import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
@@ -29,6 +32,8 @@
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.FlowEngine
+import com.android.credentialmanager.TAG
import com.android.credentialmanager.ui.screens.LoadingScreen
import com.android.credentialmanager.ui.screens.single.passkey.SinglePasskeyScreen
import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
@@ -44,6 +49,7 @@
@Composable
fun WearApp(
viewModel: CredentialSelectorViewModel,
+ flowEngine: FlowEngine = viewModel,
onCloseApp: () -> Unit,
) {
val navController = rememberSwipeDismissableNavController()
@@ -52,7 +58,6 @@
rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
WearNavScaffold(
startDestination = Screen.Loading.route,
navController = navController,
@@ -61,11 +66,11 @@
composable(Screen.Loading.route) {
LoadingScreen()
}
-
scrollable(Screen.SinglePasswordScreen.route) {
SinglePasswordScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
@@ -88,10 +93,13 @@
credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
screenIcon = null,
columnState = it.columnState,
- )
+ )
}
}
-
+ BackHandler(true) {
+ viewModel.back()
+ }
+ Log.d(TAG, "uiState change, state: $uiState")
when (val state = uiState) {
CredentialSelectorUiState.Idle -> {
if (navController.currentDestination?.route != Screen.Loading.route) {
@@ -142,7 +150,7 @@
}
}
- is CredentialSelectorUiState.Get.MultipleEntry -> {
+ is MultipleEntry -> {
navController.navigateToMultipleCredentialsFoldScreen()
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 7cd6bb3..8e5a866 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -15,32 +15,40 @@
*/
package com.android.credentialmanager.ui.components
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Icon
import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Chip
+import androidx.core.graphics.drawable.toBitmap
import androidx.wear.compose.material.ChipColors
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.Color
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Text
import com.android.credentialmanager.R
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChip.TOPPADDING
+/* Used as credential suggestion or user action chip. */
@Composable
fun CredentialsScreenChip(
label: String,
onClick: () -> Unit,
secondaryLabel: String? = null,
icon: Drawable? = null,
+ isAuthenticationEntryLocked: Boolean = false,
modifier: Modifier = Modifier,
colors: ChipColors = ChipDefaults.secondaryChipColors(),
) {
@@ -56,18 +64,37 @@
val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
secondaryLabel?.let {
{
- Text(
- text = secondaryLabel,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
+ Row {
+ Text(
+ text = secondaryLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+
+ if (isAuthenticationEntryLocked)
+ // TODO(b/324465527) change this to lock icon and correct size once figma mocks are
+ // updated
+ Icon(
+ bitmap = checkNotNull(icon?.toBitmap()?.asImageBitmap()),
+ // Decorative purpose only.
+ contentDescription = null,
+ modifier = Modifier.size(20.dp),
+ tint = Color.Unspecified
+ )
+ }
}
}
val iconParam: (@Composable BoxScope.() -> Unit)? =
- icon?.let {
+ icon?.toBitmap()?.asImageBitmap()?.let {
{
- ChipDefaults.IconSize
+ Icon(
+ bitmap = it,
+ // Decorative purpose only.
+ contentDescription = null,
+ modifier = Modifier.size(32.dp),
+ tint = Color.Unspecified
+ )
}
}
@@ -139,6 +166,37 @@
)
}
+@Composable
+fun SignInOnPhoneChip(onClick: () -> Unit) {
+ CredentialsScreenChip(
+ label = stringResource(R.string.sign_in_on_phone_button),
+ onClick = onClick,
+ modifier = Modifier
+ .padding(top = TOPPADDING),
+ )
+}
+
+@Composable
+fun LockedProviderChip(
+ authenticationEntryInfo: AuthenticationEntryInfo,
+ onClick: () -> Unit
+) {
+ val secondaryLabel = stringResource(
+ if (authenticationEntryInfo.isUnlockedAndEmpty)
+ R.string.locked_credential_entry_label_subtext_no_sign_in
+ else R.string.locked_credential_entry_label_subtext_tap_to_unlock
+ )
+
+ CredentialsScreenChip(
+ label = authenticationEntryInfo.title,
+ icon = authenticationEntryInfo.icon,
+ secondaryLabel = secondaryLabel,
+ isAuthenticationEntryLocked = !authenticationEntryInfo.isUnlockedAndEmpty,
+ onClick = onClick,
+ modifier = Modifier.padding(top = TOPPADDING),
+ )
+}
+
@Preview
@Composable
fun DismissChipPreview() {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
index 1ddf4af..423662c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
@@ -27,10 +27,12 @@
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import androidx.wear.compose.material.Text
+import androidx.compose.ui.graphics.Color
import androidx.compose.material3.Icon
import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
import androidx.compose.ui.text.style.TextAlign
+/* Used as header across Credential Selector screens. */
@Composable
fun SignInHeader(
icon: Drawable?,
@@ -46,7 +48,9 @@
bitmap = icon.toBitmap().asImageBitmap(),
modifier = Modifier.size(32.dp),
// Decorative purpose only.
- contentDescription = null
+ contentDescription = null,
+ tint = Color.Unspecified,
+
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 5898a40..03b0931 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -47,6 +47,7 @@
)
},
actionEntryList = providerInfos.flatMap { it.actionEntryList },
+ authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index a0ea4ee..5515c86 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -39,6 +39,7 @@
import com.android.credentialmanager.ui.components.CredentialsScreenChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
+import com.android.credentialmanager.ui.components.LockedProviderChip
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
@@ -142,7 +143,16 @@
)
}
}
- item { SignInOptionsChip(onSignInOptionsClicked) }
+
+ state.authenticationEntryList.forEach { authenticationEntryInfo ->
+ item {
+ LockedProviderChip(authenticationEntryInfo) {
+ // TODO(b/322797032) invoke LockedProviderScreen here using flow engine
+ }
+ }
+ }
+
+ item { SignInOptionsChip(onSignInOptionsClicked)}
item { DismissChip(onCancelClicked) }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 4c7f583..1f1a296 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -18,22 +18,19 @@
package com.android.credentialmanager.ui.screens.single.password
+import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
+import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.ui.components.PasswordRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
@@ -41,71 +38,30 @@
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasswordScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- SinglePasswordScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onPasswordInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-private fun SinglePasswordScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel,
+ flowEngine: FlowEngine,
) {
+ val launcher = rememberLauncherForActivityResult(
+ StartBalIntentSenderForResultContract()
+ ) {
+ flowEngine.sendSelectionResult(entry, it.resultCode, it.data)
+ }
SingleAccountScreen(
headerContent = {
SignInHeader(
@@ -124,9 +80,13 @@
) {
item {
Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
+ ContinueChip {
+ entry.getIntentSenderRequest()?.let {
+ launcher.launch(it)
+ } ?: Log.w(TAG, "Cannot parse IntentSenderRequest")
+ }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
deleted file mode 100644
index 8debecb..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0N
- *
- * 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.credentialmanager.ui.screens.single.password
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.single.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasswordScreenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- @MainThread
- fun initialize(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- }
-
- fun onPasswordInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index b646da4..98a5a67 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -59,5 +59,69 @@
lint: {
error_checks: ["Recycle"],
},
- generate_product_characteristics_rro: true,
+}
+
+android_app {
+ name: "PackageInstaller_tablet",
+ defaults: ["platform_app_defaults"],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ certificate: "platform",
+ privileged: true,
+ platform_apis: false,
+ sdk_version: "system_current",
+ rename_resources_package: false,
+ overrides: ["PackageInstaller"],
+
+ static_libs: [
+ "xz-java",
+ "androidx.leanback_leanback",
+ "androidx.fragment_fragment",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-extensions",
+ "android.content.pm.flags-aconfig-java",
+ "android.os.flags-aconfig-java",
+ ],
+ aaptflags: ["--product tablet"],
+
+ lint: {
+ error_checks: ["Recycle"],
+ },
+}
+
+android_app {
+ name: "PackageInstaller_tv",
+ defaults: ["platform_app_defaults"],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ certificate: "platform",
+ privileged: true,
+ platform_apis: false,
+ sdk_version: "system_current",
+ rename_resources_package: false,
+ overrides: ["PackageInstaller"],
+
+ static_libs: [
+ "xz-java",
+ "androidx.leanback_leanback",
+ "androidx.annotation_annotation",
+ "androidx.fragment_fragment",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-extensions",
+ "android.content.pm.flags-aconfig-java",
+ "android.os.flags-aconfig-java",
+ ],
+ aaptflags: ["--product tv"],
+
+ lint: {
+ error_checks: ["Recycle"],
+ },
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index cf2f85e..634e067 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,6 +20,7 @@
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -27,10 +28,10 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
-import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -200,7 +201,7 @@
params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
- if (pfd != null) {
+ if (pfd != null && Flags.readInstallInfo()) {
try {
final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 7240fb9..904e184 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,6 +31,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -397,7 +398,10 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
+ String resolvedPath = null;
+ if (info != null && Flags.getResolvedApkPath()) {
+ resolvedPath = info.getResolvedBaseApkPath();
+ }
if (info == null || !info.isSealed() || resolvedPath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index aeabbd5..22caabd 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,6 +25,7 @@
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
@@ -362,7 +363,7 @@
params.setPermissionState(
Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
)
- if (pfd != null) {
+ if (pfd != null && Flags.readInstallInfo()) {
try {
val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
params.setAppPackageName(installInfo.packageName)
@@ -425,7 +426,8 @@
if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
val info = packageInstaller.getSessionInfo(sessionId)
- val resolvedPath = info?.resolvedBaseApkPath
+ val resolvedPath =
+ if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
if (info == null || !info.isSealed || resolvedPath == null) {
Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index 464328e..67386d1 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,4 +1,4 @@
-set noparent
+include platform/frameworks/base:/packages/SettingsLib/OWNERS
chaohuiw@google.com
hanxu@google.com
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 2ce3c66..bdc6a68 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -26,6 +27,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -37,7 +39,8 @@
errorMessage: String? = null,
singleLine: Boolean = true,
enabled: Boolean = true,
- onTextChange: (String) -> Unit,
+ shape: Shape = OutlinedTextFieldDefaults.shape,
+ onTextChange: (String) -> Unit
) {
OutlinedTextField(
modifier = Modifier
@@ -55,7 +58,8 @@
if (errorMessage != null) {
Text(text = errorMessage)
}
- }
+ },
+ shape = shape
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
index a0149da..1a04bb8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -37,11 +37,13 @@
import androidx.compose.ui.semantics.Role
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.SettingsDialog
+import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsDialogItem
data class ListPreferenceOption(
val id: Int,
val text: String,
+ val summary: String = String()
)
/**
@@ -129,6 +131,14 @@
) {
RadioButton(selected = selected, onClick = null, enabled = enabled)
Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd))
- SettingsDialogItem(text = option.text, enabled = enabled)
+ Column {
+ SettingsDialogItem(text = option.text, enabled = enabled)
+ if (option.summary != String()) {
+ SettingsBody(
+ body = option.summary,
+ maxLines = 1
+ )
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index e36572f..3216e37 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -24,7 +24,6 @@
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -46,14 +45,14 @@
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.weight(1f)) {
- Preference(remember {
+ Preference(
object : PreferenceModel {
override val title = title
override val summary = summary
override val icon = icon
override val onClick = onClick
}
- })
+ )
}
PreferenceDivider()
widget()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
index 796ac48..417ce6e 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -123,6 +123,26 @@
}
@Test
+ fun click_optionsNotEmptyAndItemHasSummary_itemShowSummary() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options =
+ listOf(ListPreferenceOption(id = 1, text = "A", summary = "A_Summary"))
+ override val selectedId = mutableIntStateOf(1)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+ composeTestRule.onNodeWithText("A_Summary").assertIsDisplayed()
+ }
+
+ @Test
fun select() {
val selectedId = mutableIntStateOf(1)
composeTestRule.setContent {
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index d622eb8..6a1ee3a 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -23,3 +23,24 @@
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
bug: "316985153"
}
+
+flag {
+ name: "legacy_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the legacy le audio sharing UI."
+ bug: "322295262"
+}
+
+flag {
+ name: "enable_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates whether to enable LE audio sharing"
+ bug: "305620450"
+}
+
+flag {
+ name: "enable_le_audio_qr_code_private_broadcast_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates whether to enable LE audio private broadcast sharing via QR code"
+ bug: "308368124"
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 249fa7f..e489bc5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1702,7 +1702,8 @@
}
public boolean isPrivateProfile() {
- return UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
+ return android.os.Flags.allowPrivateProfile()
+ && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index 1ff2bef..5e3bd9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -43,5 +43,5 @@
/**
* Bluetooth scheme.
*/
- public static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
+ public static final String SCHEME_BT_BROADCAST_METADATA = "BLUETOOTH:UUID:184F;";
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index 9bb11f8..da1fd55 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -31,38 +31,34 @@
object BluetoothLeBroadcastMetadataExt {
private const val TAG = "BtLeBroadcastMetadataExt"
- // BluetoothLeBroadcastMetadata
- private const val KEY_BT_QR_VER = "R"
- private const val KEY_BT_ADDRESS_TYPE = "T"
- private const val KEY_BT_DEVICE = "D"
- private const val KEY_BT_ADVERTISING_SID = "AS"
- private const val KEY_BT_BROADCAST_ID = "B"
+ // Data Elements for directing Broadcast Assistants
private const val KEY_BT_BROADCAST_NAME = "BN"
- private const val KEY_BT_PUBLIC_BROADCAST_DATA = "PM"
- private const val KEY_BT_SYNC_INTERVAL = "SI"
- private const val KEY_BT_BROADCAST_CODE = "C"
- private const val KEY_BT_SUBGROUPS = "SG"
- private const val KEY_BT_VENDOR_SPECIFIC = "V"
- private const val KEY_BT_ANDROID_VERSION = "VN"
+ private const val KEY_BT_ADVERTISER_ADDRESS_TYPE = "AT"
+ private const val KEY_BT_ADVERTISER_ADDRESS = "AD"
+ private const val KEY_BT_BROADCAST_ID = "BI"
+ private const val KEY_BT_BROADCAST_CODE = "BC"
+ private const val KEY_BT_STREAM_METADATA = "MD"
+ private const val KEY_BT_STANDARD_QUALITY = "SQ"
+ private const val KEY_BT_HIGH_QUALITY = "HQ"
- // Subgroup data
+ // Extended Bluetooth URI Data Elements
+ private const val KEY_BT_ADVERTISING_SID = "AS"
+ private const val KEY_BT_PA_INTERVAL = "PI"
+ private const val KEY_BT_NUM_SUBGROUPS = "NS"
+
+ // Subgroup data elements
private const val KEY_BTSG_BIS_SYNC = "BS"
- private const val KEY_BTSG_BIS_MASK = "BM"
- private const val KEY_BTSG_AUDIO_CONTENT = "AC"
+ private const val KEY_BTSG_NUM_BISES = "NB"
+ private const val KEY_BTSG_METADATA = "SM"
- // Vendor specific data
- private const val KEY_BTVSD_COMPANY_ID = "VI"
- private const val KEY_BTVSD_VENDOR_DATA = "VD"
+ // Vendor specific data, not being used
+ private const val KEY_BTVSD_VENDOR_DATA = "VS"
private const val DELIMITER_KEY_VALUE = ":"
- private const val DELIMITER_BT_LEVEL_1 = ";"
- private const val DELIMITER_BT_LEVEL_2 = ","
+ private const val DELIMITER_ELEMENT = ";"
private const val SUFFIX_QR_CODE = ";;"
- private const val ANDROID_VER = "U"
- private const val QR_CODE_VER = 0x010000
-
// BT constants
private const val BIS_SYNC_MAX_CHANNEL = 32
private const val BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFu
@@ -71,33 +67,55 @@
/**
* Converts [BluetoothLeBroadcastMetadata] to QR code string.
*
- * QR code string will prefix with "BT:".
+ * QR code string will prefix with "BLUETOOTH:UUID:184F".
*/
fun BluetoothLeBroadcastMetadata.toQrCodeString(): String {
val entries = mutableListOf<Pair<String, String>>()
- entries.add(Pair(KEY_BT_QR_VER, QR_CODE_VER.toString()))
- entries.add(Pair(KEY_BT_ADDRESS_TYPE, this.sourceAddressType.toString()))
- entries.add(Pair(KEY_BT_DEVICE, this.sourceDevice.address.replace(":", "-")))
- entries.add(Pair(KEY_BT_ADVERTISING_SID, this.sourceAdvertisingSid.toString()))
- entries.add(Pair(KEY_BT_BROADCAST_ID, this.broadcastId.toString()))
- if (this.broadcastName != null) {
- entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString(
- this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)))
- }
- if (this.publicBroadcastMetadata != null) {
- entries.add(Pair(KEY_BT_PUBLIC_BROADCAST_DATA, Base64.encodeToString(
- this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
- }
- entries.add(Pair(KEY_BT_SYNC_INTERVAL, this.paSyncInterval.toString()))
+ // Generate data elements for directing Broadcast Assistants
+ require(this.broadcastName != null) { "Broadcast name is mandatory for QR code" }
+ entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString(
+ this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)))
+ entries.add(Pair(KEY_BT_ADVERTISER_ADDRESS_TYPE, this.sourceAddressType.toString()))
+ entries.add(Pair(KEY_BT_ADVERTISER_ADDRESS, this.sourceDevice.address.replace(":", "")))
+ entries.add(Pair(KEY_BT_BROADCAST_ID, String.format("%X", this.broadcastId.toLong())))
if (this.broadcastCode != null) {
entries.add(Pair(KEY_BT_BROADCAST_CODE,
Base64.encodeToString(this.broadcastCode, Base64.NO_WRAP)))
}
+ if (this.publicBroadcastMetadata != null &&
+ this.publicBroadcastMetadata?.rawMetadata?.size != 0) {
+ entries.add(Pair(KEY_BT_STREAM_METADATA, Base64.encodeToString(
+ this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
+ }
+ if ((this.audioConfigQuality and
+ BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD) != 0) {
+ entries.add(Pair(KEY_BT_STANDARD_QUALITY, "1"))
+ }
+ if ((this.audioConfigQuality and
+ BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_HIGH) != 0) {
+ entries.add(Pair(KEY_BT_HIGH_QUALITY, "1"))
+ }
+
+ // Generate extended Bluetooth URI data elements
+ entries.add(Pair(KEY_BT_ADVERTISING_SID,
+ String.format("%X", this.sourceAdvertisingSid.toLong())))
+ entries.add(Pair(KEY_BT_PA_INTERVAL, String.format("%X", this.paSyncInterval.toLong())))
+ entries.add(Pair(KEY_BT_NUM_SUBGROUPS, String.format("%X", this.subgroups.size.toLong())))
+
this.subgroups.forEach {
- subgroup -> entries.add(Pair(KEY_BT_SUBGROUPS, subgroup.toQrCodeString())) }
- entries.add(Pair(KEY_BT_ANDROID_VERSION, ANDROID_VER))
+ val (bisSync, bisCount) = getBisSyncFromChannels(it.channels)
+ entries.add(Pair(KEY_BTSG_BIS_SYNC, String.format("%X", bisSync.toLong())))
+ if (bisCount > 0u) {
+ entries.add(Pair(KEY_BTSG_NUM_BISES, String.format("%X", bisCount.toLong())))
+ }
+ if (it.contentMetadata.rawMetadata.size != 0) {
+ entries.add(Pair(KEY_BTSG_METADATA,
+ Base64.encodeToString(it.contentMetadata.rawMetadata, Base64.NO_WRAP)))
+ }
+ }
+
val qrCodeString = SCHEME_BT_BROADCAST_METADATA +
- entries.toQrCodeString(DELIMITER_BT_LEVEL_1) + SUFFIX_QR_CODE
+ entries.toQrCodeString(DELIMITER_ELEMENT) + SUFFIX_QR_CODE
Log.d(TAG, "Generated QR string : $qrCodeString")
return qrCodeString
}
@@ -105,7 +123,7 @@
/**
* Converts QR code string to [BluetoothLeBroadcastMetadata].
*
- * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:".
+ * QR code string should prefix with "BLUETOOTH:UUID:184F".
*/
fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? {
if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) {
@@ -126,15 +144,6 @@
}
}
- private fun BluetoothLeBroadcastSubgroup.toQrCodeString(): String {
- val entries = mutableListOf<Pair<String, String>>()
- entries.add(Pair(KEY_BTSG_BIS_SYNC, getBisSyncFromChannels(this.channels).toString()))
- entries.add(Pair(KEY_BTSG_BIS_MASK, getBisMaskFromChannels(this.channels).toString()))
- entries.add(Pair(KEY_BTSG_AUDIO_CONTENT,
- Base64.encodeToString(this.contentMetadata.rawMetadata, Base64.NO_WRAP)))
- return entries.toQrCodeString(DELIMITER_BT_LEVEL_2)
- }
-
private fun List<Pair<String, String>>.toQrCodeString(delimiter: String): String {
val entryStrings = this.map{ it.first + DELIMITER_KEY_VALUE + it.second }
return entryStrings.joinToString(separator = delimiter)
@@ -143,23 +152,29 @@
@TargetApi(Build.VERSION_CODES.TIRAMISU)
private fun parseQrCodeToMetadata(input: String): BluetoothLeBroadcastMetadata {
// Split into a list of list
- val level1Fields = input.split(DELIMITER_BT_LEVEL_1)
+ val elementFields = input.split(DELIMITER_ELEMENT)
.map{it.split(DELIMITER_KEY_VALUE, limit = 2)}
- var qrCodeVersion = -1
+
var sourceAddrType = BluetoothDevice.ADDRESS_TYPE_UNKNOWN
var sourceAddrString: String? = null
var sourceAdvertiserSid = -1
var broadcastId = -1
var broadcastName: String? = null
- var publicBroadcastMetadata: BluetoothLeAudioContentMetadata? = null
+ var streamMetadata: BluetoothLeAudioContentMetadata? = null
var paSyncInterval = -1
var broadcastCode: ByteArray? = null
- // List of VendorID -> Data Pairs
- var vendorDataList = mutableListOf<Pair<Int, ByteArray?>>()
- var androidVersion: String? = null
+ var audioConfigQualityStandard = -1
+ var audioConfigQualityHigh = -1
+ var numSubgroups = -1
+
+ // List of subgroup data
+ var subgroupBisSyncList = mutableListOf<UInt>()
+ var subgroupNumOfBisesList = mutableListOf<UInt>()
+ var subgroupMetadataList = mutableListOf<ByteArray?>()
+
val builder = BluetoothLeBroadcastMetadata.Builder()
- for (field: List<String> in level1Fields) {
+ for (field: List<String> in elementFields) {
if (field.isEmpty()) {
continue
}
@@ -167,190 +182,200 @@
// Ignore 3rd value and after
val value = if (field.size > 1) field[1] else ""
when (key) {
- KEY_BT_QR_VER -> {
- require(qrCodeVersion == -1) { "Duplicate qrCodeVersion: $input" }
- qrCodeVersion = value.toInt()
+ // Parse data elements for directing Broadcast Assistants
+ KEY_BT_BROADCAST_NAME -> {
+ require(broadcastName == null) { "Duplicate broadcastName: $input" }
+ broadcastName = String(Base64.decode(value, Base64.NO_WRAP))
}
- KEY_BT_ADDRESS_TYPE -> {
+ KEY_BT_ADVERTISER_ADDRESS_TYPE -> {
require(sourceAddrType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
"Duplicate sourceAddrType: $input"
}
sourceAddrType = value.toInt()
}
- KEY_BT_DEVICE -> {
+ KEY_BT_ADVERTISER_ADDRESS -> {
require(sourceAddrString == null) { "Duplicate sourceAddr: $input" }
- sourceAddrString = value.replace("-", ":")
- }
- KEY_BT_ADVERTISING_SID -> {
- require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" }
- sourceAdvertiserSid = value.toInt()
+ sourceAddrString = value.chunked(2).joinToString(":")
}
KEY_BT_BROADCAST_ID -> {
require(broadcastId == -1) { "Duplicate broadcastId: $input" }
- broadcastId = value.toInt()
- }
- KEY_BT_BROADCAST_NAME -> {
- require(broadcastName == null) { "Duplicate broadcastName: $input" }
- broadcastName = String(Base64.decode(value, Base64.NO_WRAP))
- }
- KEY_BT_PUBLIC_BROADCAST_DATA -> {
- require(publicBroadcastMetadata == null) {
- "Duplicate publicBroadcastMetadata $input"
- }
- publicBroadcastMetadata = BluetoothLeAudioContentMetadata
- .fromRawBytes(Base64.decode(value, Base64.NO_WRAP))
- }
- KEY_BT_SYNC_INTERVAL -> {
- require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" }
- paSyncInterval = value.toInt()
+ broadcastId = value.toInt(16)
}
KEY_BT_BROADCAST_CODE -> {
require(broadcastCode == null) { "Duplicate broadcastCode: $input" }
- broadcastCode = Base64.decode(value, Base64.NO_WRAP)
+
+ broadcastCode = Base64.decode(value.dropLastWhile { it.equals(0.toByte()) }
+ .toByteArray(), Base64.NO_WRAP)
}
- KEY_BT_ANDROID_VERSION -> {
- require(androidVersion == null) { "Duplicate androidVersion: $input" }
- androidVersion = value
- Log.i(TAG, "QR code Android version: $androidVersion")
+ KEY_BT_STREAM_METADATA -> {
+ require(streamMetadata == null) {
+ "Duplicate streamMetadata $input"
+ }
+ streamMetadata = BluetoothLeAudioContentMetadata
+ .fromRawBytes(Base64.decode(value, Base64.NO_WRAP))
}
- // Repeatable
- KEY_BT_SUBGROUPS -> {
- builder.addSubgroup(parseSubgroupData(value))
+ KEY_BT_STANDARD_QUALITY -> {
+ require(audioConfigQualityStandard == -1) {
+ "Duplicate audioConfigQualityStandard: $input"
+ }
+ audioConfigQualityStandard = value.toInt()
}
- // Repeatable
- KEY_BT_VENDOR_SPECIFIC -> {
- vendorDataList.add(parseVendorData(value))
+ KEY_BT_HIGH_QUALITY -> {
+ require(audioConfigQualityHigh == -1) {
+ "Duplicate audioConfigQualityHigh: $input"
+ }
+ audioConfigQualityHigh = value.toInt()
+ }
+
+ // Parse extended Bluetooth URI data elements
+ KEY_BT_ADVERTISING_SID -> {
+ require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" }
+ sourceAdvertiserSid = value.toInt(16)
+ }
+ KEY_BT_PA_INTERVAL -> {
+ require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" }
+ paSyncInterval = value.toInt(16)
+ }
+ KEY_BT_NUM_SUBGROUPS -> {
+ require(numSubgroups == -1) { "Duplicate numSubgroups: $input" }
+ numSubgroups = value.toInt(16)
+ }
+
+ // Repeatable subgroup elements
+ KEY_BTSG_BIS_SYNC -> {
+ subgroupBisSyncList.add(value.toUInt(16))
+ }
+ KEY_BTSG_NUM_BISES -> {
+ subgroupNumOfBisesList.add(value.toUInt(16))
+ }
+ KEY_BTSG_METADATA -> {
+ subgroupMetadataList.add(Base64.decode(value, Base64.NO_WRAP))
}
}
}
- Log.d(TAG, "parseQrCodeToMetadata: sourceAddrType=$sourceAddrType, " +
+ Log.d(TAG, "parseQrCodeToMetadata: main data elements sourceAddrType=$sourceAddrType, " +
"sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " +
"broadcastId=$broadcastId, broadcastName=$broadcastName, " +
- "publicBroadcastMetadata=${publicBroadcastMetadata != null}, " +
+ "streamMetadata=${streamMetadata != null}, " +
"paSyncInterval=$paSyncInterval, " +
- "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}")
- Log.d(TAG, "Not used in current code, but part of the specification: " +
- "qrCodeVersion=$qrCodeVersion, androidVersion=$androidVersion, " +
- "vendorDataListSize=${vendorDataList.size}")
+ "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}, " +
+ "audioConfigQualityStandard=$audioConfigQualityStandard, " +
+ "audioConfigQualityHigh=$audioConfigQualityHigh")
+
val adapter = BluetoothAdapter.getDefaultAdapter()
+ // Check parsed elements data
+ require(broadcastName != null) {
+ "broadcastName($broadcastName) must present in QR code string"
+ }
+ var addr = sourceAddrString
+ var addrType = sourceAddrType
+ if (sourceAddrString != null) {
+ require(sourceAddrType != BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
+ "sourceAddrType($sourceAddrType) must present if address present"
+ }
+ } else {
+ // Use placeholder device if not present
+ addr = "FF:FF:FF:FF:FF:FF"
+ addrType = BluetoothDevice.ADDRESS_TYPE_RANDOM
+ }
+ val device = adapter.getRemoteLeDevice(requireNotNull(addr), addrType)
+
// add source device and set broadcast code
- val device = adapter.getRemoteLeDevice(requireNotNull(sourceAddrString), sourceAddrType)
+ var audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE or
+ (if (audioConfigQualityStandard != -1) audioConfigQualityStandard else 0) or
+ (if (audioConfigQualityHigh != -1) audioConfigQualityHigh else 0)
+
+ // process subgroup data
+ // metadata should include at least 1 subgroup for metadata, add a placeholder group if not present
+ numSubgroups = if (numSubgroups > 0) numSubgroups else 1
+ for (i in 0 until numSubgroups) {
+ val bisSync = subgroupBisSyncList.getOrNull(i)
+ val bisNum = subgroupNumOfBisesList.getOrNull(i)
+ val metadata = subgroupMetadataList.getOrNull(i)
+
+ val channels = convertToChannels(bisSync, bisNum)
+ val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
+ .setAudioLocation(0).build()
+ val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+ setCodecId(SUBGROUP_LC3_CODEC_ID)
+ setCodecSpecificConfig(audioCodecConfigMetadata)
+ setContentMetadata(
+ BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0)))
+ channels.forEach(::addChannel)
+ }.build()
+
+ Log.d(TAG, "parseQrCodeToMetadata: subgroup $i elements bisSync=$bisSync, " +
+ "bisNum=$bisNum, metadata=${metadata != null}")
+
+ builder.addSubgroup(subgroup)
+ }
+
builder.apply {
setSourceDevice(device, sourceAddrType)
setSourceAdvertisingSid(sourceAdvertiserSid)
setBroadcastId(broadcastId)
setBroadcastName(broadcastName)
- setPublicBroadcast(publicBroadcastMetadata != null)
- setPublicBroadcastMetadata(publicBroadcastMetadata)
+ // QR code should set PBP(public broadcast profile) for auracast
+ setPublicBroadcast(true)
+ setPublicBroadcastMetadata(streamMetadata)
setPaSyncInterval(paSyncInterval)
setEncrypted(broadcastCode != null)
setBroadcastCode(broadcastCode)
// Presentation delay is unknown and not useful when adding source
// Broadcast sink needs to sync to the Broadcast source to get presentation delay
setPresentationDelayMicros(0)
+ setAudioConfigQuality(audioConfigQuality)
}
return builder.build()
}
- private fun parseSubgroupData(input: String): BluetoothLeBroadcastSubgroup {
- Log.d(TAG, "parseSubgroupData: $input")
- val fields = input.split(DELIMITER_BT_LEVEL_2)
- var bisSync: UInt? = null
- var bisMask: UInt? = null
- var metadata: ByteArray? = null
-
- fields.forEach { field ->
- val(key, value) = field.split(DELIMITER_KEY_VALUE)
- when (key) {
- KEY_BTSG_BIS_SYNC -> {
- require(bisSync == null) { "Duplicate bisSync: $input" }
- bisSync = value.toUInt()
- }
- KEY_BTSG_BIS_MASK -> {
- require(bisMask == null) { "Duplicate bisMask: $input" }
- bisMask = value.toUInt()
- }
- KEY_BTSG_AUDIO_CONTENT -> {
- require(metadata == null) { "Duplicate metadata: $input" }
- metadata = Base64.decode(value, Base64.NO_WRAP)
- }
- }
- }
- val channels = convertToChannels(requireNotNull(bisSync), requireNotNull(bisMask))
- val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
- .setAudioLocation(0).build()
- return BluetoothLeBroadcastSubgroup.Builder().apply {
- setCodecId(SUBGROUP_LC3_CODEC_ID)
- setCodecSpecificConfig(audioCodecConfigMetadata)
- setContentMetadata(
- BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0)))
- channels.forEach(::addChannel)
- }.build()
- }
-
- private fun parseVendorData(input: String): Pair<Int, ByteArray?> {
- var companyId = -1
- var data: ByteArray? = null
- val fields = input.split(DELIMITER_BT_LEVEL_2)
- fields.forEach { field ->
- val(key, value) = field.split(DELIMITER_KEY_VALUE)
- when (key) {
- KEY_BTVSD_COMPANY_ID -> {
- require(companyId == -1) { "Duplicate companyId: $input" }
- companyId = value.toInt()
- }
- KEY_BTVSD_VENDOR_DATA -> {
- require(data == null) { "Duplicate data: $input" }
- data = Base64.decode(value, Base64.NO_WRAP)
- }
- }
- }
- return Pair(companyId, data)
- }
-
- private fun getBisSyncFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt {
+ private fun getBisSyncFromChannels(
+ channels: List<BluetoothLeBroadcastChannel>
+ ): Pair<UInt, UInt> {
var bisSync = 0u
- // channel index starts from 1
- channels.forEach { channel ->
- if (channel.isSelected && channel.channelIndex > 0) {
- bisSync = bisSync or (1u shl (channel.channelIndex - 1))
- }
- }
- // No channel is selected means no preference on Android platform
- return if (bisSync == 0u) BIS_SYNC_NO_PREFERENCE else bisSync
- }
-
- private fun getBisMaskFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt {
- var bisMask = 0u
+ var bisCount = 0u
// channel index starts from 1
channels.forEach { channel ->
if (channel.channelIndex > 0) {
- bisMask = bisMask or (1u shl (channel.channelIndex - 1))
+ bisCount++
+ if (channel.isSelected) {
+ bisSync = bisSync or (1u shl (channel.channelIndex - 1))
+ }
}
}
- return bisMask
+ // No channel is selected means no preference on Android platform
+ return if (bisSync == 0u) Pair(BIS_SYNC_NO_PREFERENCE, bisCount)
+ else Pair(bisSync, bisCount)
}
- private fun convertToChannels(bisSync: UInt, bisMask: UInt):
- List<BluetoothLeBroadcastChannel> {
- Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisMask=$bisMask")
- var selectionMask = bisSync
- if (bisSync != BIS_SYNC_NO_PREFERENCE) {
- require(bisMask == (bisMask or bisSync)) {
- "bisSync($bisSync) must select a subset of bisMask($bisMask) if it has preferences"
- }
- } else {
- // No channel preference means no channel is selected
- selectionMask = 0u
- }
+ private fun convertToChannels(
+ bisSync: UInt?,
+ bisNum: UInt?
+ ): List<BluetoothLeBroadcastChannel> {
+ Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisNum=$bisNum")
+ // if no BIS_SYNC or BIS_NUM available or BIS_SYNC is no preference
+ // return empty channel map with one placeholder channel
+ var selectedChannels = if (bisSync != null && bisNum != null) bisSync else 0u
val channels = mutableListOf<BluetoothLeBroadcastChannel>()
val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
.setAudioLocation(0).build()
+
+ if (bisSync == BIS_SYNC_NO_PREFERENCE || selectedChannels == 0u) {
+ // No channel preference means no channel is selected
+ // Generate one placeholder channel for metadata
+ val channel = BluetoothLeBroadcastChannel.Builder().apply {
+ setSelected(false)
+ setChannelIndex(1)
+ setCodecMetadata(audioCodecConfigMetadata)
+ }
+ return listOf(channel.build())
+ }
+
for (i in 0 until BIS_SYNC_MAX_CHANNEL) {
val channelMask = 1u shl i
- if ((bisMask and channelMask) != 0u) {
+ if ((selectedChannels and channelMask) != 0u) {
val channel = BluetoothLeBroadcastChannel.Builder().apply {
- setSelected((selectionMask and channelMask) != 0u)
+ setSelected(true)
setChannelIndex(i + 1)
setCodecMetadata(audioCodecConfigMetadata)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 581c7de..a376c1f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -92,13 +92,13 @@
}
}
- protected String mPackageName;
+ @NonNull protected final String mPackageName;
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
- public InfoMediaManager(
+ /* package */ InfoMediaManager(
Context context,
@NonNull String packageName,
Notification notification,
@@ -112,7 +112,7 @@
/** Creates an instance of InfoMediaManager. */
public static InfoMediaManager createInstance(
Context context,
- String packageName,
+ @Nullable String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) {
@@ -148,8 +148,7 @@
}
private void updateRouteListingPreference() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && !TextUtils.isEmpty(mPackageName)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RouteListingPreference routeListingPreference =
getRouteListingPreference();
Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference,
@@ -218,11 +217,7 @@
protected final void rebuildDeviceList() {
mMediaDevices.clear();
mCurrentConnectedDevice = null;
- if (TextUtils.isEmpty(mPackageName)) {
- buildAllRoutes();
- } else {
- buildAvailableRoutes();
- }
+ buildAvailableRoutes();
}
protected final void notifyCurrentConnectedDeviceChanged() {
@@ -250,12 +245,8 @@
return;
}
- if (TextUtils.isEmpty(mPackageName)) {
- connectDeviceWithoutPackageName(device);
- } else {
- device.setConnectedRecord();
- transferToRoute(device.mRouteInfo);
- }
+ device.setConnectedRecord();
+ transferToRoute(device.mRouteInfo);
}
/**
@@ -265,11 +256,6 @@
* @return If add device successful return {@code true}, otherwise return {@code false}
*/
boolean addDeviceToPlayMedia(MediaDevice device) {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
- return false;
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null || !info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
@@ -306,7 +292,6 @@
boolean preferRouteListingOrdering() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && !TextUtils.isEmpty(mPackageName)
&& Api34Impl.preferRouteListingOrdering(getRouteListingPreference());
}
@@ -326,11 +311,6 @@
* @return If device stop successful return {@code true}, otherwise return {@code false}
*/
boolean removeDeviceFromPlayMedia(MediaDevice device) {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "removeDeviceFromMedia() package name is null or empty!");
- return false;
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null || !info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
@@ -346,11 +326,6 @@
* Release session to stop playing media on MediaDevice.
*/
boolean releaseSession() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "releaseSession() package name is null or empty!");
- return false;
- }
-
final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
if (sessionInfo == null) {
Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
@@ -367,11 +342,6 @@
*/
@NonNull
List<MediaDevice> getSelectableMediaDevices() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "getSelectableMediaDevices() package name is null or empty!");
- return Collections.emptyList();
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null) {
Log.w(TAG, "getSelectableMediaDevices() cannot find selectable MediaDevice from : "
@@ -394,11 +364,6 @@
*/
@NonNull
List<MediaDevice> getDeselectableMediaDevices() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.d(TAG, "getDeselectableMediaDevices() package name is null or empty!");
- return Collections.emptyList();
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null) {
Log.d(TAG, "getDeselectableMediaDevices() cannot find deselectable MediaDevice from : "
@@ -462,11 +427,6 @@
* @param volume the value of volume
*/
void adjustSessionVolume(int volume) {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
- return;
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null) {
Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
@@ -484,11 +444,6 @@
* @return maximum volume of the session, and return -1 if not found.
*/
public int getSessionVolumeMax() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
- return -1;
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null) {
Log.w(TAG, "getSessionVolumeMax() can't find corresponding RoutingSession with : "
@@ -505,11 +460,6 @@
* @return current volume of the session, and return -1 if not found.
*/
public int getSessionVolume() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "getSessionVolume() package name is null or empty!");
- return -1;
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null) {
Log.w(TAG, "getSessionVolume() can't find corresponding RoutingSession with : "
@@ -521,11 +471,6 @@
}
CharSequence getSessionName() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "Unable to get session name. The package name is null or empty!");
- return null;
- }
-
final RoutingSessionInfo info = getRoutingSessionInfo();
if (info == null) {
Log.w(TAG, "Unable to get session name for package: " + mPackageName);
@@ -548,20 +493,6 @@
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
- private void buildAllRoutes() {
- for (MediaRoute2Info route : getAllRoutes()) {
- if (DEBUG) {
- Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
- + route.getVolume() + ", type : " + route.getType());
- }
- if (route.isSystemRoute()) {
- addMediaDevice(route);
- }
- }
- }
-
- // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
- @SuppressWarnings("NewApi")
private synchronized void buildAvailableRoutes() {
for (MediaRoute2Info route : getAvailableRoutes()) {
if (DEBUG) {
@@ -594,8 +525,7 @@
infos.add(transferableRoute);
}
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && !TextUtils.isEmpty(mPackageName)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RouteListingPreference routeListingPreference = getRouteListingPreference();
if (routeListingPreference != null) {
final List<RouteListingPreference.Item> preferenceRouteListing =
@@ -679,7 +609,7 @@
break;
}
- if (mediaDevice != null && !TextUtils.isEmpty(mPackageName)
+ if (mediaDevice != null
&& getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
mediaDevice.setState(STATE_SELECTED);
if (mCurrentConnectedDevice == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 97bbf12..cf8906d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -51,9 +51,9 @@
private final Executor mExecutor = Executors.newSingleThreadExecutor();
- public ManagerInfoMediaManager(
+ /* package */ ManagerInfoMediaManager(
Context context,
- String packageName,
+ @NonNull String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) {
super(context, packageName, notification, localBluetoothManager);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index 9d578bc..5b1c8ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -41,7 +41,7 @@
NoOpInfoMediaManager(
Context context,
- String packageName,
+ @NonNull String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) {
super(context, packageName, notification, localBluetoothManager);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index aef09ac..c8c8b672 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -65,9 +65,9 @@
};
// TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
- public RouterInfoMediaManager(
+ /* package */ RouterInfoMediaManager(
Context context,
- String packageName,
+ @NonNull String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 213a66e..1ad7d49 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -22,21 +22,30 @@
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
+import android.os.Flags;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ApplicationsStateTest {
+ private static final int APP_ENTRY_ID = 1;
private ApplicationsState.AppEntry mEntry;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
- mEntry = mock(ApplicationsState.AppEntry.class);
- mEntry.info = mock(ApplicationInfo.class);
+ mEntry = new ApplicationsState.AppEntry(
+ ApplicationProvider.getApplicationContext(),
+ mock(ApplicationInfo.class),
+ APP_ENTRY_ID);
}
@Test
@@ -310,6 +319,8 @@
@Test
public void testPrivateProfileFilterDisplaysCorrectApps() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
mEntry.showInPersonalTab = true;
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
@@ -320,4 +331,14 @@
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isTrue();
}
+
+ @Test
+ public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mEntry.showInPersonalTab = false;
+ mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+ assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
+ }
}
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 f0330c4..290e63ca 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
@@ -212,28 +212,6 @@
}
@Test
- public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- }
-
- @Test
public void onPreferredFeaturesChanged_samePackageName_shouldAddMediaDevice() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
@@ -436,29 +414,6 @@
}
@Test
- public void onRoutesChanged_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
- when(info.getDeduplicationIds()).thenReturn(Set.of());
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- }
-
- @Test
public void hasPreferenceRouteListing_oldSdkVersion_returnsFalse() {
assertThat(mInfoMediaManager.preferRouteListingOrdering()).isFalse();
}
@@ -587,36 +542,6 @@
}
@Test
- public void onRoutesRemoved_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- when(mRouterManager.getAllRoutes()).thenReturn(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- }
-
- @Test
- public void addDeviceToPlayMedia_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
- final MediaDevice device = mock(MediaDevice.class);
-
- assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isFalse();
- }
-
- @Test
public void addDeviceToPlayMedia_containSelectableRoutes_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -660,14 +585,6 @@
}
@Test
- public void removeDeviceFromMedia_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
- final MediaDevice device = mock(MediaDevice.class);
-
- assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isFalse();
- }
-
- @Test
public void removeDeviceFromMedia_containSelectedRoutes_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -711,13 +628,6 @@
}
@Test
- public void getSelectableMediaDevice_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSelectableMediaDevices()).isEmpty();
- }
-
- @Test
public void getSelectableMediaDevice_notContainPackageName_returnEmpty() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -730,13 +640,6 @@
}
@Test
- public void getDeselectableMediaDevice_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getDeselectableMediaDevices()).isEmpty();
- }
-
- @Test
public void getDeselectableMediaDevice_checkList() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -761,20 +664,6 @@
}
@Test
- public void adjustSessionVolume_packageNameIsNull_noCrash() {
- mInfoMediaManager.mPackageName = null;
-
- mInfoMediaManager.adjustSessionVolume(10);
- }
-
- @Test
- public void getSessionVolumeMax_packageNameIsNull_returnNotFound() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
- }
-
- @Test
public void getSessionVolumeMax_containPackageName_returnMaxVolume() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -800,13 +689,6 @@
}
@Test
- public void getSessionVolume_packageNameIsNull_returnNotFound() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
- }
-
- @Test
public void getSessionVolume_containPackageName_returnMaxVolume() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -841,13 +723,6 @@
}
@Test
- public void releaseSession_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.releaseSession()).isFalse();
- }
-
- @Test
public void releaseSession_removeSuccessfully_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -860,13 +735,6 @@
}
@Test
- public void getSessionName_packageNameIsNull_returnNull() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSessionName()).isNull();
- }
-
- @Test
public void getSessionName_routeSessionInfoIsNull_returnNull() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = null;
@@ -942,32 +810,6 @@
}
@Test
- public void onTransferred_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- mInfoMediaManager.registerCallback(mCallback);
-
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onTransferred(sessionInfo, sessionInfo);
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- verify(mCallback).onConnectedDeviceChanged(null);
- }
-
- @Test
public void onSessionUpdated_shouldDispatchDeviceListAdded() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
@@ -978,7 +820,6 @@
routes.add(info);
mShadowRouter2Manager.setAllRoutes(routes);
- mInfoMediaManager.mPackageName = "";
mInfoMediaManager.registerCallback(mCallback);
mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
index fde378f..3adb204 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -27,12 +27,13 @@
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
+import java.util.ArrayList;
import java.util.List;
@Implements(MediaRouter2Manager.class)
public class ShadowRouter2Manager {
- private List<MediaRoute2Info> mAvailableRoutes;
+ private List<MediaRoute2Info> mAvailableRoutes = new ArrayList<>();
private List<MediaRoute2Info> mAllRoutes;
private List<MediaRoute2Info> mDeselectableRoutes;
private List<RoutingSessionInfo> mRemoteSessions;
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
index 27d7078..1ad20dc 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
@@ -32,7 +32,7 @@
class BluetoothLeBroadcastMetadataExtTest {
@Test
- fun toQrCodeString() {
+ fun toQrCodeString_encrypted() {
val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
setCodecId(0x6)
val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
@@ -70,6 +70,37 @@
}
@Test
+ fun toQrCodeString_non_encrypted() {
+ val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+ setCodecId(0x6)
+ val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
+ setContentMetadata(BluetoothLeAudioContentMetadata.Builder()
+ .build())
+ setCodecSpecificConfig(audioCodecConfigMetadata)
+ addChannel(BluetoothLeBroadcastChannel.Builder().apply {
+ setSelected(true)
+ setChannelIndex(1)
+ setCodecMetadata(audioCodecConfigMetadata)
+ }.build())
+ }.build()
+
+ val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
+ setSourceDevice(DevicePublic, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+ setSourceAdvertisingSid(1)
+ setBroadcastId(0xDE51E9)
+ setBroadcastName("Hockey")
+ setAudioConfigQuality(BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD)
+ setPaSyncInterval(0xFFFF)
+ setEncrypted(false)
+ addSubgroup(subgroup)
+ }.build()
+
+ val qrCodeString = metadata.toQrCodeString()
+
+ assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED)
+ }
+
+ @Test
fun toQrCodeString_NoChannelSelected() {
val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
setCodecId(0x6)
@@ -102,6 +133,7 @@
addSubgroup(subgroup)
}.build()
+ // if no channel is selected, no preference(0xFFFFFFFFu) will be set in BIS
val qrCodeString = metadata.toQrCodeString()
val parsedMetadata =
@@ -111,13 +143,11 @@
assertThat(parsedMetadata.subgroups).isNotNull()
assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
- assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
+ assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse()
- // Input order does not matter due to parsing through bisMask
+ // placeholder channel with not selected
assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
- assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
- assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse()
}
@Test
@@ -162,13 +192,11 @@
assertThat(parsedMetadata.subgroups).isNotNull()
assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
- // Only selected channel can be recovered
- assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
+ // Only selected channel can be recovered, non-selected ones will be ignored
+ assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue()
- assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
- assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
- assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
- assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue()
+ assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(2)
+ assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isTrue()
}
@Test
@@ -180,16 +208,34 @@
assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
}
+ @Test
+ fun decodeAndEncodeAgain_sameString_non_encrypted() {
+ val metadata =
+ BluetoothLeBroadcastMetadataExt
+ .convertToBroadcastMetadata(QR_CODE_STRING_NON_ENCRYPTED)!!
+
+ val qrCodeString = metadata.toQrCodeString()
+
+ assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED)
+ }
+
private companion object {
const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+ const val TEST_DEVICE_ADDRESS_PUBLIC = "AA:BB:CC:00:11:22"
val Device: BluetoothDevice =
BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS,
BluetoothDevice.ADDRESS_TYPE_RANDOM)
+ val DevicePublic: BluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS_PUBLIC,
+ BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+
const val QR_CODE_STRING =
- "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" +
- "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" +
- "VN:U;;"
+ "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" +
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"
+ const val QR_CODE_STRING_NON_ENCRYPTED =
+ "BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" +
+ "NS:1;BS:1;NB:1;;"
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f2a7105..4ed1965 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -317,7 +317,7 @@
description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
" central place, instead of reading resources directly. This is to take into account display"
" cutouts and other special cases. "
- bug: "317199366"
+ bug: "317016114"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -451,6 +451,16 @@
}
flag {
+ name: "slice_manager_binder_call_background"
+ namespace: "systemui"
+ description: "Move the ISliceManager#getPinnedSpecs binder call to the background thread."
+ bug: "322745650"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "register_new_wallet_card_in_background"
namespace: "systemui"
description: "Decide whether the call to registerNewWalletCards method should be issued on background thread."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index e4dc9be..5d5f12e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -123,7 +123,7 @@
val views = LinkedList<View>().apply { add(view) }
while (views.isNotEmpty()) {
- val v = views.removeFirst()
+ val v = views.removeAt(0)
if (v.background != null) {
return v.background
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index aa56736..76931a2 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,12 +22,15 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.internal.policy.ScreenDecorationsUtils
@@ -89,12 +92,18 @@
) {
activity.setContent {
PlatformTheme {
- CommunalHub(
- viewModel = viewModel,
- onOpenWidgetPicker = onOpenWidgetPicker,
- widgetConfigurator = widgetConfigurator,
- onEditDone = onEditDone,
- )
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(LocalAndroidColorScheme.current.outlineVariant),
+ ) {
+ CommunalHub(
+ viewModel = viewModel,
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ widgetConfigurator = widgetConfigurator,
+ onEditDone = onEditDone,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index bc85513..be5aa8a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -1,6 +1,7 @@
package com.android.systemui.communal.ui.compose
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -12,6 +13,7 @@
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.FixedSizeEdgeDetector
+import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -21,6 +23,7 @@
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.animation.scene.updateSceneTransitionLayoutState
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -31,16 +34,25 @@
object Communal {
object Elements {
+ val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker)
val Content = ElementKey("CommunalContent")
}
}
val sceneTransitions = transitions {
- from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) {
- spec = tween(durationMillis = 500)
-
+ to(TransitionSceneKey.Communal) {
+ spec = tween(durationMillis = 1000)
translate(Communal.Elements.Content, Edge.Right)
- fade(Communal.Elements.Content)
+ timestampRange(startMillis = 167, endMillis = 334) {
+ fade(Communal.Elements.Scrim)
+ fade(Communal.Elements.Content)
+ }
+ }
+ to(TransitionSceneKey.Blank) {
+ spec = tween(durationMillis = 1000)
+ translate(Communal.Elements.Content, Edge.Right)
+ timestampRange(endMillis = 167) { fade(Communal.Elements.Content) }
+ timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
}
}
@@ -111,6 +123,12 @@
viewModel: BaseCommunalViewModel,
modifier: Modifier = Modifier,
) {
+ Box(
+ modifier =
+ Modifier.element(Communal.Elements.Scrim)
+ .fillMaxSize()
+ .background(LocalAndroidColorScheme.current.outlineVariant),
+ )
Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 090750e..cddd4fa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -141,7 +141,6 @@
modifier =
modifier
.fillMaxSize()
- .background(LocalAndroidColorScheme.current.outlineVariant)
.pointerInput(gridState, contentOffset, contentListState) {
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 828e34d..2e781e6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -29,13 +29,17 @@
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.scale
-import androidx.compose.ui.layout.IntermediateMeasureScope
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
@@ -91,23 +95,7 @@
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
key: ElementKey,
-): Modifier {
- return this.then(ElementModifier(layoutImpl, scene, key))
- // TODO(b/311132415): Move this into ElementNode once we can create a delegate
- // IntermediateLayoutModifierNode.
- .intermediateLayout { measurable, constraints ->
- // TODO(b/311132415): No need to fetch the element and sceneState from the map anymore
- // once this is merged into ElementNode.
- val element = layoutImpl.elements.getValue(key)
- val sceneState = element.sceneStates.getValue(scene.key)
-
- val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints)
- layout(placeable.width, placeable.height) {
- place(layoutImpl, scene, element, sceneState, placeable, placementScope = this)
- }
- }
- .testTag(key.testTag)
-}
+): Modifier = this.then(ElementModifier(layoutImpl, scene, key)).testTag(key.testTag)
/**
* An element associated to [ElementNode]. Note that this element does not support updates as its
@@ -129,7 +117,7 @@
private var layoutImpl: SceneTransitionLayoutImpl,
private var scene: Scene,
private var key: ElementKey,
-) : Modifier.Node(), DrawModifierNode {
+) : Modifier.Node(), DrawModifierNode, ApproachLayoutModifierNode {
private var _element: Element? = null
private val element: Element
get() = _element!!
@@ -197,6 +185,31 @@
maybePruneMaps(layoutImpl, prevElement, prevSceneState)
}
+ override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ // TODO(b/324191441): Investigate whether making this check more complex (checking if this
+ // element is shared or transformed) would lead to better performance.
+ return layoutImpl.state.currentTransition == null
+ }
+
+ override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ lookaheadCoordinates: LayoutCoordinates
+ ): Boolean {
+ // TODO(b/324191441): Investigate whether making this check more complex (checking if this
+ // element is shared or transformed) would lead to better performance.
+ return layoutImpl.state.currentTransition == null
+ }
+
+ @ExperimentalComposeUiApi
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints)
+ return layout(placeable.width, placeable.height) {
+ place(layoutImpl, scene, element, sceneState, placeable, placementScope = this)
+ }
+ }
+
override fun ContentDrawScope.draw() {
val drawScale = getDrawScale(layoutImpl, element, scene)
if (drawScale == Scale.Default) {
@@ -368,7 +381,7 @@
}
@OptIn(ExperimentalComposeUiApi::class)
-private fun IntermediateMeasureScope.measure(
+private fun ApproachMeasureScope.measure(
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
element: Element,
@@ -431,7 +444,7 @@
}
@OptIn(ExperimentalComposeUiApi::class)
-private fun IntermediateMeasureScope.place(
+private fun ApproachMeasureScope.place(
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
element: Element,
@@ -439,6 +452,8 @@
placeable: Placeable,
placementScope: Placeable.PlacementScope,
) {
+ this as LookaheadScope
+
with(placementScope) {
// Update the offset (relative to the SceneTransitionLayout) this element has in this scene
// when idle.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 1cdba2d..662f33f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -154,7 +154,7 @@
stateLinks: List<StateLink> = emptyList(),
): SceneTransitionLayoutState {
return remember {
- HoistedSceneTransitionLayoutScene(
+ HoistedSceneTransitionLayoutState(
currentScene,
transitions,
onChangeScene,
@@ -364,7 +364,7 @@
* A [SceneTransitionLayout] whose current scene/source of truth is hoisted (its current value comes
* from outside).
*/
-internal class HoistedSceneTransitionLayoutScene(
+internal class HoistedSceneTransitionLayoutState(
initialScene: SceneKey,
override var transitions: SceneTransitions,
private var changeScene: (SceneKey) -> Unit,
@@ -400,7 +400,7 @@
// late.
val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
animateToScene(
- layoutState = this@HoistedSceneTransitionLayoutScene,
+ layoutState = this@HoistedSceneTransitionLayoutState,
target = newKey,
transitionKey = null,
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index c9b5b75..33be1dc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -44,7 +44,6 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -248,11 +247,9 @@
}
@Test
- @Ignore
- fun elementIsReusedInSameSceneAndBetweenScenes() {
+ fun elementIsReusedBetweenScenes() {
var currentScene by mutableStateOf(TestScenes.SceneA)
var sceneCState by mutableStateOf(0)
- var sceneDState by mutableStateOf(0)
val key = TestElements.Foo
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -270,19 +267,6 @@
scene(TestScenes.SceneC) {
when (sceneCState) {
0 -> Row(Modifier.element(key)) {}
- 1 -> Column(Modifier.element(key)) {}
- else -> {
- /* Nothing */
- }
- }
- }
- scene(TestScenes.SceneD) {
- // We should be able to extract the modifier before assigning it to different
- // nodes.
- val childModifier = Modifier.element(key)
- when (sceneDState) {
- 0 -> Row(childModifier) {}
- 1 -> Column(childModifier) {}
else -> {
/* Nothing */
}
@@ -315,35 +299,10 @@
assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneC)
- // Scene C, state 1: the same element is reused.
+ // Scene C, state 1: the element is removed from the map.
sceneCState = 1
rule.waitForIdle()
- assertThat(layoutImpl.elements.keys).containsExactly(key)
- assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneC)
-
- // Scene D, state 0: the same element is reused.
- currentScene = TestScenes.SceneD
- sceneDState = 0
- rule.waitForIdle()
-
- assertThat(layoutImpl.elements.keys).containsExactly(key)
- assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneD)
-
- // Scene D, state 1: the same element is reused.
- sceneDState = 1
- rule.waitForIdle()
-
- assertThat(layoutImpl.elements.keys).containsExactly(key)
- assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneD)
-
- // Scene D, state 2: the element is removed from the map.
- sceneDState = 2
- rule.waitForIdle()
-
assertThat(element.sceneStates).isEmpty()
assertThat(layoutImpl.elements).isEmpty()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 8a35ef1..a6715df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -8,7 +8,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -47,7 +47,7 @@
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
@Mock private lateinit var configController: ConfigurationController
- @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
+ @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
private val logBuffer = FakeLogBuffer.Factory.create()
private lateinit var controller: DreamOverlayAnimationsController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 9368097..3484025 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -197,7 +197,16 @@
runCurrent()
}
- assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+ assertThat(startedSteps)
+ .isEqualTo(
+ listOf(
+ // The initial transition will also get sent when collect started
+ TransitionStep(OFF, LOCKSCREEN, 0f, STARTED),
+ steps[0],
+ steps[3],
+ steps[6]
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
index 837a9db..d33c10e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
@@ -20,6 +20,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -66,6 +67,25 @@
}
@Test
+ fun alpha_WhenNotGone_clockMigrationFlagIsOff_emitsKeyguardAlpha() =
+ testScope.runTest {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ keyguardRepository.setKeyguardAlpha(0.5f)
+ assertThat(alpha).isEqualTo(0.5f)
+
+ keyguardRepository.setKeyguardAlpha(0.8f)
+ assertThat(alpha).isEqualTo(0.8f)
+ }
+
+ @Test
fun alpha_WhenGoneToAod() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
@@ -112,6 +132,7 @@
@Test
fun alpha_whenGone_equalsZero() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
val alpha by collectLastValue(underTest.alpha)
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
new file mode 100644
index 0000000..4defe8a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DreamingToGlanceableHubTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val underTest by lazy { kosmos.dreamingToGlanceableHubTransitionViewModel }
+
+ @Test
+ fun dreamOverlayAlpha() =
+ testScope.runTest {
+ val values by collectValues(underTest.dreamOverlayAlpha)
+ assertThat(values).isEmpty()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ // Should start running here...
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.5f),
+ // Up to here...
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun dreamOverlayTranslationX() =
+ testScope.runTest {
+ val values by collectValues(underTest.dreamOverlayTranslationX(100))
+ assertThat(values).isEmpty()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0.3f),
+ step(0.6f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(3)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = value,
+ transitionState = state,
+ ownerName = "DreamingToGlanceableHubTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..64125f1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GlanceableHubToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val configurationRepository = kosmos.fakeConfigurationRepository
+ val underTest by lazy { kosmos.glanceableHubToLockscreenTransitionViewModel }
+
+ @Test
+ fun lockscreenFadeIn() =
+ testScope.runTest {
+ val values by collectValues(underTest.keyguardAlpha)
+ assertThat(values).containsExactly(0f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ // Should start running here...
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(0.4f),
+ // ...up to here
+ step(0.5f),
+ step(0.6f),
+ step(0.7f),
+ step(0.8f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun lockscreenTranslationX() =
+ testScope.runTest {
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
+ 100
+ )
+ val values by collectValues(underTest.keyguardTranslationX)
+ assertThat(values).isEmpty()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.3f),
+ step(0.5f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(5)
+ values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = this::class.java.simpleName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
new file mode 100644
index 0000000..241d0b8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenToGlanceableHubTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val configurationRepository = kosmos.fakeConfigurationRepository
+ val underTest by lazy { kosmos.lockscreenToGlanceableHubTransitionViewModel }
+
+ @Test
+ fun lockscreenFadeOut() =
+ testScope.runTest {
+ val values by collectValues(underTest.keyguardAlpha)
+ assertThat(values).containsExactly(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ // Should start running here
+ step(0f, TransitionState.STARTED),
+ step(0.1f),
+ step(0.2f),
+ // ...up to here
+ step(0.3f),
+ step(0.4f),
+ step(0.5f),
+ step(0.6f),
+ step(0.7f),
+ step(0.8f),
+ // ...up to here
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun lockscreenTranslationX() =
+ testScope.runTest {
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
+ -100
+ )
+ val values by collectValues(underTest.keyguardTranslationX)
+ assertThat(values).isEmpty()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.3f),
+ step(0.5f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(5)
+ values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = value,
+ transitionState = state,
+ ownerName = this::class.java.simpleName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 15cf83c..47e1ee9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -119,15 +119,19 @@
fun lockscreenAlpha_runDimissFromKeyguard() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
runCurrent()
- sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope,
+ )
- keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
- keyguardTransitionRepository.sendTransitionStep(step(1f))
-
- assertThat(values.size).isEqualTo(2)
- values.forEach { assertThat(it).isEqualTo(1f) }
+ assertThat(values[0]).isEqualTo(1f)
+ assertThat(values[1]).isEqualTo(1f)
+ // Ensure FINISHED sets alpha to 0
+ assertThat(values[2]).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index 2ea12ef..8ae9172 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.mockito.any
@@ -65,6 +66,7 @@
MockitoAnnotations.initMocks(this)
whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
+ whenever(currentTilesInteractor.currentTiles).thenReturn(MutableStateFlow(emptyList()))
}
@Test
@@ -201,6 +203,45 @@
assertThat(autoAddedTiles).doesNotContain(SPEC)
}
+ @Test
+ fun autoAddable_trackIfNotAdded_currentTile_markedAsAdded() =
+ testScope.runTest {
+ val fakeTile = FakeQSTile(USER).apply { tileSpec = SPEC.spec }
+ val fakeCurrentTileModel = TileModel(SPEC, fakeTile)
+ whenever(currentTilesInteractor.currentTiles)
+ .thenReturn(MutableStateFlow(listOf(fakeCurrentTileModel)))
+
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+ runCurrent()
+
+ assertThat(autoAddedTiles).contains(SPEC)
+ }
+
+ @Test
+ fun autoAddable_trackIfNotAdded_tileAddedToCurrentTiles_markedAsAdded() =
+ testScope.runTest {
+ val fakeTile = FakeQSTile(USER).apply { tileSpec = SPEC.spec }
+ val fakeCurrentTileModel = TileModel(SPEC, fakeTile)
+ val currentTilesFlow = MutableStateFlow(emptyList<TileModel>())
+
+ whenever(currentTilesInteractor.currentTiles).thenReturn(currentTilesFlow)
+
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+ runCurrent()
+
+ assertThat(autoAddedTiles).doesNotContain(SPEC)
+
+ currentTilesFlow.value = listOf(fakeCurrentTileModel)
+
+ assertThat(autoAddedTiles).contains(SPEC)
+ }
+
private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
return AutoAddInteractor(
autoAddables,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
new file mode 100644
index 0000000..a5c5544
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.content.ComponentName
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.componentName
+import com.android.systemui.qs.external.iQSTileService
+import com.android.systemui.qs.external.tileServiceManagerFacade
+import com.android.systemui.qs.external.tileServicesFacade
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileDataInteractorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ componentName = TEST_COMPONENT
+ tileSpec = TileSpec.create(componentName)
+ }
+ private val underTest =
+ with(kosmos) {
+ CustomTileDataInteractor(
+ tileSpec = tileSpec,
+ defaultsRepository = customTileDefaultsRepository,
+ serviceInteractor = customTileServiceInteractor,
+ customTileInteractor = customTileInteractor,
+ packageUpdatesRepository = customTilePackagesUpdatesRepository,
+ userRepository = userRepository,
+ tileScope = testScope.backgroundScope,
+ )
+ }
+
+ private suspend fun setup() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(TEST_USER_1))
+ fakeUserRepository.setSelectedUserInfo(TEST_USER_1)
+ }
+ }
+
+ @Test
+ fun activeTileIsNotBoundUntilDataCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(true)
+
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun notActiveTileIsNotBoundUntilDataCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(false)
+
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun tileIsUnboundWhenDataIsNotListened() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(false)
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+ val dataJob =
+ underTest
+ .tileData(TEST_USER_1.userHandle, flowOf(DataUpdateTrigger.InitialRequest))
+ .launchIn(backgroundScope)
+ runCurrent()
+ tileServiceManagerFacade.processPendingBind()
+ assertThat(iQSTileService.isTileListening).isTrue()
+ assertThat(tileServiceManagerFacade.isBound).isTrue()
+
+ dataJob.cancel()
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun tileDataCollection() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+ val tileData by
+ collectLastValue(
+ underTest.tileData(
+ TEST_USER_1.userHandle,
+ flowOf(DataUpdateTrigger.InitialRequest)
+ )
+ )
+ runCurrent()
+ tileServicesFacade.customTileInterface!!.updateTileState(TEST_TILE, 1)
+
+ runCurrent()
+
+ with(tileData!!) {
+ assertThat(user.identifier).isEqualTo(TEST_USER_1.id)
+ assertThat(componentName).isEqualTo(componentName)
+ assertThat(tile).isEqualTo(TEST_TILE)
+ assertThat(callingAppUid).isEqualTo(1)
+ assertThat(hasPendingBind).isEqualTo(true)
+ assertThat(isToggleable).isEqualTo(false)
+ assertThat(defaultTileIcon).isEqualTo(TEST_TILE.icon)
+ assertThat(defaultTileLabel).isEqualTo(TEST_TILE.label)
+ }
+ }
+ }
+
+ @Test
+ fun tileAvailableWhenDefaultsAreLoaded() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf(true)).inOrder()
+ }
+ }
+
+ @Test
+ fun tileUnavailableWhenDefaultsAreNotLoaded() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Error,
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf(false)).inOrder()
+ }
+ }
+
+ @Test
+ fun tileAvailabilityUndefinedWhenDefaultsAreLoadedForAnotherUser() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_2.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Error,
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf()).inOrder()
+ }
+ }
+
+ private companion object {
+
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+ val TEST_USER_1 = UserInfo(1, "first user", UserInfo.FLAG_MAIN)
+ val TEST_USER_2 = UserInfo(2, "second user", UserInfo.FLAG_MAIN)
+ val TEST_TILE =
+ Tile().apply {
+ label = "test_tile_1"
+ icon = Icon.createWithContentUri("file://test_1")
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 995d6ac..9546a32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,7 +25,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -35,6 +34,7 @@
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -50,16 +50,16 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileInteractor =
with(kosmos) {
CustomTileInteractor(
- tileSpec,
- customTileDefaultsRepository,
- customTileRepository,
- testScope.backgroundScope,
- testScope.testScheduler,
+ tileSpec = tileSpec,
+ defaultsRepository = customTileDefaultsRepository,
+ customTileRepository = customTileRepository,
+ tileScope = testScope.backgroundScope,
+ backgroundContext = testScope.testScheduler,
)
}
@@ -69,14 +69,14 @@
testScope.runTest {
customTileRepository.setTileActive(true)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- underTest.initForUser(TEST_USER)
+ underTest.initForUser(TEST_USER_1)
- assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
- assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
}
}
@@ -86,18 +86,18 @@
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = launch { underTest.initForUser(TEST_USER) }
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch { underTest.initForUser(TEST_USER_1) }
- underTest.updateTile(TEST_TILE)
+ underTest.updateTile(TEST_TILE_1)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
}
}
@@ -107,34 +107,34 @@
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = launch { underTest.initForUser(TEST_USER) }
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch { underTest.initForUser(TEST_USER_1) }
- customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
- customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+ customTileDefaultsRepository.putDefaults(TEST_USER_1, TEST_COMPONENT, TEST_DEFAULTS)
+ customTileDefaultsRepository.requestNewDefaults(TEST_USER_1, TEST_COMPONENT)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
}
}
@Test(expected = IllegalStateException::class)
fun getTileBeforeInitThrows() =
- with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
+ with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER_1) } }
@Test
fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(true)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER_1) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
@@ -149,12 +149,12 @@
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER_1) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
@@ -176,18 +176,89 @@
}
}
+ @Test
+ fun activeFollowsTheRepository() {
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ customTileRepository.setTileActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun initForTheSameUserProcessedOnce() =
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch {
+ underTest.initForUser(TEST_USER_1)
+ underTest.initForUser(TEST_USER_1)
+ }
+
+ underTest.updateTile(TEST_TILE_1)
+ runCurrent()
+ initJob.join()
+
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
+ }
+ }
+
+ @Test
+ fun initForDifferentUsersProcessedOnce() =
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
+ )
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier),
+ TEST_TILE_2,
+ )
+ val tiles1 by collectValues(underTest.getTiles(TEST_USER_1))
+ val tiles2 by collectValues(underTest.getTiles(TEST_USER_2))
+
+ val initJob = launch {
+ underTest.initForUser(TEST_USER_1)
+ underTest.initForUser(TEST_USER_2)
+ }
+ runCurrent()
+ initJob.join()
+
+ assertThat(tiles1).isEmpty()
+ assertThat(tiles2).hasSize(1)
+ assertThat(tiles2.last()).isEqualTo(TEST_TILE_2)
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
- val TEST_USER = UserHandle.of(1)!!
- val TEST_TILE by lazy {
+ val TEST_USER_1 = UserHandle.of(1)!!
+ val TEST_USER_2 = UserHandle.of(2)!!
+ val TEST_TILE_1 by lazy {
Tile().apply {
label = "test_tile_1"
icon = Icon.createWithContentUri("file://test_1")
}
}
- val TEST_DEFAULTS by lazy {
- CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+ val TEST_TILE_2 by lazy {
+ Tile().apply {
+ label = "test_tile_2"
+ icon = Icon.createWithContentUri("file://test_2")
+ }
}
+ val TEST_DEFAULTS by lazy { CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
new file mode 100644
index 0000000..a2127a4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -0,0 +1,265 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.app.IUriGrantsManager
+import android.content.ComponentName
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.widget.Button
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileMapperTest : SysuiTestCase() {
+
+ private val uriGrantsManager: IUriGrantsManager = mock {}
+ private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+ private val underTest by lazy {
+ CustomTileMapper(
+ context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
+ uriGrantsManager = uriGrantsManager,
+ )
+ }
+
+ @Test
+ fun stateHasPendingBinding() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(hasPendingBind = true),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ actions = setOf(QSTileState.UserAction.LONG_CLICK),
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateActive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_ACTIVE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.ACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateInactive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_INACTIVE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateUnavailable() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_UNAVAILABLE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ actions = setOf(QSTileState.UserAction.LONG_CLICK),
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun tileWithChevron() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(isToggleable = false),
+ )
+ val expected =
+ createTileState(
+ sideIcon = QSTileState.SideViewIcon.Chevron,
+ a11yClass = Button::class.qualifiedName,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun defaultIconFallback() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileIcon = createIcon(RuntimeException(), false)),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ icon = DEFAULT_DRAWABLE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun failedToLoadIconTileIsInactive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(
+ tileIcon = createIcon(RuntimeException(), false),
+ defaultTileIcon = createIcon(null, true)
+ ),
+ )
+ val expected =
+ createTileState(
+ icon = null,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ private fun Kosmos.createModel(
+ tileState: Int = Tile.STATE_ACTIVE,
+ tileIcon: Icon = createIcon(DRAWABLE, false),
+ hasPendingBind: Boolean = false,
+ isToggleable: Boolean = true,
+ defaultTileIcon: Icon = createIcon(DEFAULT_DRAWABLE, true),
+ ) =
+ CustomTileDataModel(
+ UserHandle.of(1),
+ tileSpec.componentName,
+ Tile().apply {
+ state = tileState
+ label = "test label"
+ subtitle = "test subtitle"
+ icon = tileIcon
+ contentDescription = "test content description"
+ },
+ callingAppUid = 0,
+ hasPendingBind = hasPendingBind,
+ isToggleable = isToggleable,
+ defaultTileLabel = "test default tile label",
+ defaultTileIcon = defaultTileIcon,
+ )
+
+ private fun createIcon(drawable: Drawable?, isDefault: Boolean): Icon = mock {
+ if (isDefault) {
+ whenever(loadDrawable(any())).thenReturn(drawable)
+ } else {
+ whenever(loadDrawableCheckingUriGrant(any(), any(), any(), any())).thenReturn(drawable)
+ }
+ }
+
+ private fun createIcon(exception: RuntimeException, isDefault: Boolean): Icon = mock {
+ if (isDefault) {
+ whenever(loadDrawable(any())).thenThrow(exception)
+ } else {
+ whenever(loadDrawableCheckingUriGrant(any(), eq(uriGrantsManager), any(), any()))
+ .thenThrow(exception)
+ }
+ }
+
+ private fun createTileState(
+ activationState: QSTileState.ActivationState = QSTileState.ActivationState.ACTIVE,
+ icon: Drawable? = DRAWABLE,
+ sideIcon: QSTileState.SideViewIcon = QSTileState.SideViewIcon.None,
+ actions: Set<QSTileState.UserAction> =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ a11yClass: String? = Switch::class.qualifiedName,
+ ): QSTileState {
+ return QSTileState(
+ { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+ "test label",
+ activationState,
+ "test subtitle",
+ actions,
+ "test content description",
+ null,
+ sideIcon,
+ QSTileState.EnabledState.ENABLED,
+ a11yClass,
+ )
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+
+ val DEFAULT_DRAWABLE = TestStubDrawable("default_icon_drawable")
+ val DRAWABLE = TestStubDrawable("icon_drawable")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..c709f16
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import android.view.IWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.componentName
+import com.android.systemui.qs.external.iQSTileService
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.actions.pendingIntentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
+import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val packageManagerFacade = FakePackageManagerFacade()
+ private val windowManagerFacade = FakeWindowManagerFacade()
+ private val kosmos =
+ testKosmos().apply {
+ componentName = TEST_COMPONENT
+ tileSpec = TileSpec.create(componentName)
+ testCase = this@CustomTileUserActionInteractorTest
+ }
+
+ private val underTest =
+ with(kosmos) {
+ CustomTileUserActionInteractor(
+ context =
+ mock {
+ whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
+ },
+ tileSpec = tileSpec,
+ qsTileLogger = qsTileLogger,
+ windowManager = windowManagerFacade.windowManager,
+ displayTracker = mock {},
+ qsTileIntentUserInputHandler = inputHandler,
+ backgroundContext = testDispatcher,
+ serviceInteractor = customTileServiceInteractor,
+ )
+ }
+
+ private suspend fun setup() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(TEST_USER_1))
+ fakeUserRepository.setSelectedUserInfo(TEST_USER_1)
+ }
+ }
+
+ @Test
+ fun clickStartsActivityWhenPossible() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(
+ click(customTileModel(activityLaunchForClick = pendingIntent()))
+ )
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ assertThat(inputHandler.pendingIntentInputs).hasSize(1)
+ assertThat(iQSTileService.clicks).hasSize(0)
+ }
+ }
+
+ @Test
+ fun clickPassedToTheServiceWhenNoActivity() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ packageManagerFacade.resolutionResult = null
+ underTest.handleInput(click(customTileModel(activityLaunchForClick = null)))
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ assertThat(inputHandler.pendingIntentInputs).hasSize(0)
+ assertThat(iQSTileService.clicks).hasSize(1)
+ }
+ }
+
+ @Test
+ fun longClickOpensResolvedIntent() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ packageManagerFacade.resolutionResult =
+ ActivityInfo().apply {
+ packageName = "resolved.pkg"
+ name = "Test"
+ }
+ underTest.handleInput(longClick(customTileModel()))
+
+ assertThat(inputHandler.intentInputs).hasSize(1)
+ with(inputHandler.intentInputs.first()) {
+ assertThat(intent.action).isEqualTo(TileService.ACTION_QS_TILE_PREFERENCES)
+ assertThat(intent.component).isEqualTo(ComponentName("resolved.pkg", "Test"))
+ assertThat(
+ intent.getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ )
+ .isEqualTo(componentName)
+ assertThat(intent.getIntExtra(TileService.EXTRA_STATE, Int.MAX_VALUE))
+ .isEqualTo(111)
+ }
+ }
+ }
+
+ @Test
+ fun longClickOpensDefaultIntentWhenNoResolved() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(longClick(customTileModel()))
+
+ assertThat(inputHandler.intentInputs).hasSize(1)
+ with(inputHandler.intentInputs.first()) {
+ assertThat(intent.action)
+ .isEqualTo(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ assertThat(intent.data.toString()).isEqualTo("package:test.pkg")
+ }
+ }
+ }
+
+ @Test
+ fun revokeTokenDoesntRevokeWhenShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(true)
+
+ underTest.revokeToken(false)
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ }
+ }
+
+ @Test
+ fun forceRevokeTokenRevokesWhenShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(true)
+
+ underTest.revokeToken(true)
+
+ assertThat(windowManagerFacade.isTokenGranted).isFalse()
+ }
+ }
+
+ @Test
+ fun revokeTokenRevokesWhenNotShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(false)
+
+ underTest.revokeToken(false)
+
+ assertThat(windowManagerFacade.isTokenGranted).isFalse()
+ }
+ }
+
+ @Test
+ fun startActivityDoesntStartWithNoToken() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.startActivityAndCollapse(mock())
+
+ // Checking all types of inputs
+ assertThat(inputHandler.handledInputs).isEmpty()
+ }
+ }
+
+ private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
+
+ private fun Kosmos.customTileModel(
+ componentName: ComponentName = tileSpec.componentName,
+ activityLaunchForClick: PendingIntent? = null,
+ tileState: Int = 111,
+ ) =
+ CustomTileDataModel(
+ TEST_USER_1.userHandle,
+ componentName,
+ Tile().also {
+ it.activityLaunchForClick = activityLaunchForClick
+ it.state = tileState
+ },
+ callingAppUid = 0,
+ hasPendingBind = false,
+ isToggleable = false,
+ defaultTileLabel = "default_label",
+ defaultTileIcon = Icon.createWithContentUri("default_icon"),
+ )
+
+ private class FakePackageManagerFacade(val packageManager: PackageManager = mock()) {
+
+ var resolutionResult: ActivityInfo? = null
+
+ init {
+ whenever(packageManager.resolveActivityAsUser(any(), any<Int>(), any())).then {
+ ResolveInfo().apply { activityInfo = resolutionResult }
+ }
+ }
+ }
+
+ private class FakeWindowManagerFacade(val windowManager: IWindowManager = mock()) {
+
+ var isTokenGranted: Boolean = false
+ private set
+
+ init {
+ with(windowManager) {
+ whenever(removeWindowToken(any(), any())).then {
+ isTokenGranted = false
+ Unit
+ }
+ whenever(addWindowToken(any(), any(), any(), nullable())).then {
+ isTokenGranted = true
+ Unit
+ }
+ }
+ }
+ }
+
+ private companion object {
+
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+ val TEST_USER_1 = UserInfo(1, "first user", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
deleted file mode 100644
index d0e05fa..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
+++ /dev/null
@@ -1,614 +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.statusbar;
-
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.SparseArray;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.settings.FakeSettings;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
- @Mock
- private NotificationPresenter mPresenter;
- @Mock
- private UserManager mUserManager;
- @Mock
- private UserTracker mUserTracker;
-
- // Dependency mocks:
- @Mock
- private NotificationVisibilityProvider mVisibilityProvider;
- @Mock
- private CommonNotifCollection mNotifCollection;
- @Mock
- private DevicePolicyManager mDevicePolicyManager;
- @Mock
- private NotificationClickNotifier mClickNotifier;
- @Mock
- private OverviewProxyService mOverviewProxyService;
- @Mock
- private KeyguardManager mKeyguardManager;
- @Mock
- private DeviceProvisionedController mDeviceProvisionedController;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private KeyguardStateController mKeyguardStateController;
-
- private UserInfo mCurrentUser;
- private UserInfo mSecondaryUser;
- private UserInfo mWorkUser;
- private UserInfo mCommunalUser;
- private FakeSettings mSettings;
- private TestNotificationLockscreenUserManager mLockscreenUserManager;
- private NotificationEntry mCurrentUserNotif;
- private NotificationEntry mSecondaryUserNotif;
- private NotificationEntry mWorkProfileNotif;
- private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
- private Executor mMainExecutor = Runnable::run; // Direct executor
- private Executor mBackgroundExecutor = Runnable::run; // Direct executor
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
-
- int currentUserId = ActivityManager.getCurrentUser();
- when(mUserTracker.getUserId()).thenReturn(currentUserId);
- mSettings = new FakeSettings();
- mSettings.setUserId(ActivityManager.getCurrentUser());
- mCurrentUser = new UserInfo(currentUserId, "", 0);
- mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
- mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
- UserManager.USER_TYPE_PROFILE_MANAGED);
- mCommunalUser = new UserInfo(currentUserId + 3, "" /* name */, null /* iconPath */, 0,
- UserManager.USER_TYPE_PROFILE_COMMUNAL);
-
- when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
- when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
- mCurrentUser, mWorkUser));
- when(mUserManager.getProfilesIncludingCommunal(currentUserId)).thenReturn(
- Lists.newArrayList(mCurrentUser, mWorkUser, mCommunalUser));
- when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
- mSecondaryUser));
- when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
- Lists.newArrayList(mSecondaryUser, mCommunalUser));
-
- Notification notifWithPrivateVisibility = new Notification();
- notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
- mCurrentUserNotif = new NotificationEntryBuilder()
- .setNotification(notifWithPrivateVisibility)
- .setUser(new UserHandle(mCurrentUser.id))
- .build();
- mSecondaryUserNotif = new NotificationEntryBuilder()
- .setNotification(notifWithPrivateVisibility)
- .setUser(new UserHandle(mSecondaryUser.id))
- .build();
- mWorkProfileNotif = new NotificationEntryBuilder()
- .setNotification(notifWithPrivateVisibility)
- .setUser(new UserHandle(mWorkUser.id))
- .build();
-
- mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
- mLockscreenUserManager.setUpWithPresenter(mPresenter);
- }
-
- private void changeSetting(String setting) {
- final Collection<Uri> lockScreenUris = new ArrayList<>();
- lockScreenUris.add(Settings.Secure.getUriFor(setting));
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
- lockScreenUris, 0);
- }
-
- @Test
- public void testGetCurrentProfiles() {
- final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
- expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
- expectedCurProfiles.put(mWorkUser.id, mWorkUser);
- if (android.multiuser.Flags.supportCommunalProfile()) {
- expectedCurProfiles.put(mCommunalUser.id, mCommunalUser);
- }
- assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedCurProfiles));
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- final SparseArray<UserInfo> expectedSecProfiles = new SparseArray<>();
- expectedSecProfiles.put(mSecondaryUser.id, mSecondaryUser);
- if (android.multiuser.Flags.supportCommunalProfile()) {
- expectedSecProfiles.put(mCommunalUser.id, mCommunalUser);
- }
- assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedSecProfiles));
- }
-
- @Test
- public void testLockScreenShowNotificationsFalse() {
- mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
- }
-
- @Test
- public void testLockScreenShowNotificationsTrue() {
- mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
- }
-
- @Test
- public void testLockScreenAllowPrivateNotificationsTrue() {
- mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testLockScreenAllowPrivateNotificationsFalse() {
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
- }
-
- @Test
- public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
- }
-
- @Test
- public void testCurrentUserPrivateNotificationsNotRedacted() {
- // GIVEN current user doesn't allow private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN current user's notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- }
-
- @Test
- public void testCurrentUserPrivateNotificationsRedacted() {
- // GIVEN current user allows private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mCurrentUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN current user's notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- }
-
- @Test
- public void testWorkPrivateNotificationsRedacted() {
- // GIVEN work profile doesn't private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN work profile notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
- assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
- }
-
- @Test
- public void testWorkPrivateNotificationsNotRedacted() {
- // GIVEN work profile allows private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN work profile notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
- assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
- }
-
- @Test
- public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
- // GIVEN work profile allows private notifications to show but the other users don't
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mWorkUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN the work profile notification doesn't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
- // THEN the current user and secondary user notifications do need to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testWorkProfileRedacted_otherUsersNotRedacted() {
- // GIVEN work profile doesn't allow private notifications to show but the other users do
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mWorkUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mCurrentUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN the work profile notification needs to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
- // THEN the current user and secondary user notifications don't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testSecondaryUserNotRedacted_currentUserRedacted() {
- // GIVEN secondary profile allows private notifications to show but the current user
- // doesn't allow private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN the secondary profile notification still needs to be redacted because the current
- // user's setting takes precedence
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testUserSwitchedCallsOnUserSwitching() {
- mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
- mContext);
- verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
- }
-
- @Test
- public void testIsLockscreenPublicMode() {
- assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
- mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
- assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
- }
-
- @Test
- public void testUpdateIsPublicMode() {
- when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
-
- NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
- mLockscreenUserManager.addNotificationStateChangedListener(listener);
- mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-
- // first call explicitly sets user 0 to not public; notifies
- mLockscreenUserManager.updatePublicMode();
- assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener).onNotificationStateChanged();
- clearInvocations(listener);
-
- // calling again has no changes; does not notify
- mLockscreenUserManager.updatePublicMode();
- assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener, never()).onNotificationStateChanged();
-
- // Calling again with keyguard now showing makes user 0 public; notifies
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- mLockscreenUserManager.updatePublicMode();
- assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener).onNotificationStateChanged();
- clearInvocations(listener);
-
- // calling again has no changes; does not notify
- mLockscreenUserManager.updatePublicMode();
- assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener, never()).onNotificationStateChanged();
- }
-
- @Test
- public void testDevicePolicyDoesNotAllowNotifications() {
- // User allows them
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides notifs on lockscreen
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testDevicePolicyDoesNotAllowNotifications_secondary() {
- Mockito.clearInvocations(mDevicePolicyManager);
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides notifications
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
- .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testDevicePolicy_noPrivateNotifications() {
- Mockito.clearInvocations(mDevicePolicyManager);
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides sensitive content
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- }
-
- @Test
- public void testDevicePolicy_noPrivateNotifications_userAll() {
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides sensitive content
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
- .setNotification(new Notification())
- .setUser(UserHandle.ALL)
- .build()));
- }
-
- @Test
- public void testDevicePolicyPrivateNotifications_secondary() {
- Mockito.clearInvocations(mDevicePolicyManager);
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides sensitive content
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
- .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testHideNotifications_primary() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testHideNotifications_secondary() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testHideNotifications_secondary_userSwitch() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testShowNotifications_secondary_userSwitch() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
- // DevicePolicy allows notifications
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(0);
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- // KeyguardManager does not allow notifications
- when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
- // callback, so it's only updated when the setting is
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testUserAllowsNotificationsInPublic_settingsChange() {
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-
- // User disables
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- private class TestNotificationLockscreenUserManager
- extends NotificationLockscreenUserManagerImpl {
- public TestNotificationLockscreenUserManager(Context context) {
- super(
- context,
- mBroadcastDispatcher,
- mDevicePolicyManager,
- mUserManager,
- mUserTracker,
- (() -> mVisibilityProvider),
- (() -> mNotifCollection),
- mClickNotifier,
- (() -> mOverviewProxyService),
- NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
- mStatusBarStateController,
- mMainExecutor,
- mBackgroundExecutor,
- mDeviceProvisionedController,
- mKeyguardStateController,
- mSettings,
- mock(DumpManager.class),
- mock(LockPatternUtils.class),
- mFakeFeatureFlags);
- }
-
- public BroadcastReceiver getBaseBroadcastReceiverForTest() {
- return mBaseBroadcastReceiver;
- }
-
- public UserTracker.Callback getUserTrackerCallbackForTest() {
- return mUserChangedCallback;
- }
-
- public ContentObserver getLockscreenSettingsObserverForTest() {
- return mLockscreenSettingsObserver;
- }
-
- public ContentObserver getSettingsObserverForTest() {
- return mSettingsObserver;
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index bcc0710..d505b27 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -172,8 +172,6 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
-
int currentUserId = ActivityManager.getCurrentUser();
when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index f9546c4..162d8ae 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -17,10 +17,6 @@
<1> *;
}
--keepclasseswithmembers class * {
- public <init>(android.content.Context, android.util.AttributeSet);
-}
-
-keep class androidx.core.app.CoreComponentFactory
# Keep the wm shell lib
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a681da3..65120a9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1514,6 +1514,12 @@
<!-- The amount of vertical offset for the keyguard during the full shade transition. -->
<dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>
+ <!-- LOCKSCREEN -> GLANCEABLE_HUB transition: Amount to shift lockscreen content on entering -->
+ <dimen name="lockscreen_to_hub_transition_lockscreen_translation_x">-824dp</dimen>
+
+ <!-- GLANCEABLE_HUB -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
+ <dimen name="hub_to_lockscreen_transition_lockscreen_translation_x">824dp</dimen>
+
<!-- Distance that the full shade transition takes in order for media to fully transition to
the shade -->
<dimen name="lockscreen_shade_media_transition_distance">120dp</dimen>
@@ -1857,6 +1863,7 @@
<dimen name="dream_overlay_y_offset">80dp</dimen>
<dimen name="dream_overlay_entry_y_offset">40dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
+ <dimen name="dream_overlay_exit_x_offset">824dp</dimen>
<dimen name="status_view_margin_horizontal">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index a9928d8..63088aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -20,6 +20,7 @@
import android.app.PendingIntent;
import android.net.Uri;
+import android.os.Handler;
import android.os.Trace;
import android.provider.Settings;
import android.util.Log;
@@ -39,6 +40,9 @@
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.plugins.ActivityStarter;
@@ -60,6 +64,8 @@
Dumpable {
private static final String TAG = "KeyguardSliceViewCtrl";
+ private final Handler mHandler;
+ private final Handler mBgHandler;
private final ActivityStarter mActivityStarter;
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
@@ -105,6 +111,8 @@
@Inject
public KeyguardSliceViewController(
+ @Main Handler handler,
+ @Background Handler bgHandler,
KeyguardSliceView keyguardSliceView,
ActivityStarter activityStarter,
ConfigurationController configurationController,
@@ -112,6 +120,8 @@
DumpManager dumpManager,
DisplayTracker displayTracker) {
super(keyguardSliceView);
+ mHandler = handler;
+ mBgHandler = bgHandler;
mActivityStarter = activityStarter;
mConfigurationController = configurationController;
mTunerService = tunerService;
@@ -182,24 +192,34 @@
* Update contents of the view.
*/
public void refresh() {
- Slice slice;
+
Trace.beginSection("KeyguardSliceViewController#refresh");
- // We can optimize performance and avoid binder calls when we know that we're bound
- // to a Slice on the same process.
- if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
- KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
- if (instance != null) {
- slice = instance.onBindSlice(mKeyguardSliceUri);
+ try {
+ Slice slice;
+ if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
+ KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
+ if (instance != null) {
+ if (Flags.sliceManagerBinderCallBackground()) {
+ mBgHandler.post(() -> {
+ Slice _slice = instance.onBindSlice(mKeyguardSliceUri);
+ mHandler.post(() -> mObserver.onChanged(_slice));
+ });
+ return;
+ }
+ slice = instance.onBindSlice(mKeyguardSliceUri);
+ } else {
+ Log.w(TAG, "Keyguard slice not bound yet?");
+ slice = null;
+ }
} else {
- Log.w(TAG, "Keyguard slice not bound yet?");
- slice = null;
+ // TODO: Make SliceViewManager injectable
+ slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(
+ mKeyguardSliceUri);
}
- } else {
- // TODO: Make SliceViewManager injectable
- slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(mKeyguardSliceUri);
+ mObserver.onChanged(slice);
+ } finally {
+ Trace.endSection();
}
- mObserver.onChanged(slice);
- Trace.endSection();
}
void showSlice(Slice slice) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 23afb7c..4997370 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -62,7 +62,7 @@
userManager.getCredentialOwnerProfile(userId)
override fun getParentProfileIdOrSelfId(userId: Int): Int =
- userManager.getProfileParent(userId).id
+ userManager.getProfileParent(userId)?.id ?: userManager.getCredentialOwnerProfile(userId)
override fun verifyCredential(
request: BiometricPromptRequest.Credential,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 557ad13..9000da3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -33,20 +33,16 @@
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.DreamLog
-import com.android.systemui.res.R
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import javax.inject.Inject
import javax.inject.Named
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
@@ -58,7 +54,7 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
- private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamOverlayViewModel: DreamOverlayViewModel,
private val configController: ConfigurationController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
@@ -91,59 +87,45 @@
this.view = view
view.repeatWhenAttached {
- val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
- val configCallback =
- object : ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- configurationBasedDimensions.value = loadFromResources(view)
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int -> setElementsTranslationYAtPosition(px, position) },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
}
- configController.addCallback(configCallback)
+ launch {
+ dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int -> setElementsTranslationXAtPosition(px, position) },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
+ }
- try {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- /* Translation animations, when moving from DREAMING->LOCKSCREEN state */
- launch {
- configurationBasedDimensions
- .flatMapLatest {
- transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
- }
- .collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsTranslationYAtPosition(px, position)
- },
- POSITION_TOP or POSITION_BOTTOM
+ launch {
+ dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
)
- }
- }
-
- /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
- launch {
- transitionViewModel.dreamOverlayAlpha.collect { alpha ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = position,
- fadingOut = true,
- )
- },
- POSITION_TOP or POSITION_BOTTOM
- )
- }
- }
-
- launch {
- transitionViewModel.transitionEnded.collect { _ ->
- mOverlayStateController.setExitAnimationsRunning(false)
- }
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
}
- } finally {
- // Ensure the callback is removed when cancellation happens
- configController.removeCallback(configCallback)
+
+ launch {
+ dreamOverlayViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
+ }
}
}
}
@@ -373,14 +355,10 @@
}
}
- private fun loadFromResources(view: View): ConfigurationBasedDimensions {
- return ConfigurationBasedDimensions(
- translationYPx =
- view.resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset),
- )
+ /** Sets x translation of complications at the specified position. */
+ private fun setElementsTranslationXAtPosition(translationX: Float, position: Int) {
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+ v.translationX = translationX
+ }
}
-
- private data class ConfigurationBasedDimensions(
- val translationYPx: Int,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
new file mode 100644
index 0000000..dd67a4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.dreams.ui.viewmodel
+
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DreamOverlayViewModel
+@Inject
+constructor(
+ configurationInteractor: ConfigurationInteractor,
+ private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+ private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+) {
+
+ val dreamOverlayTranslationX: Flow<Float> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.dream_overlay_exit_x_offset)
+ .flatMapLatest { px: Int ->
+ toGlanceableHubTransitionViewModel.dreamOverlayTranslationX(px)
+ }
+
+ val dreamOverlayTranslationY: Flow<Float> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
+ .flatMapLatest { px: Int ->
+ toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
+ }
+
+ val dreamOverlayAlpha: Flow<Float> =
+ merge(
+ toLockscreenTransitionViewModel.dreamOverlayAlpha,
+ toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
+ )
+
+ val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6eff792..56162ae 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -102,11 +102,6 @@
default = true
)
- // TODO(b/301955929)
- @JvmField
- val NOTIF_LS_BACKGROUND_THREAD =
- releasedFlag("notification_lockscreen_mgr_bg_thread")
-
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index 72a81cb..0f1cc99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -20,8 +20,8 @@
value class Locked(val locked: Boolean)
enum class ModifierKey(val displayedText: String) {
- ALT("ALT LEFT"),
- ALT_GR("ALT RIGHT"),
+ ALT("ALT"),
+ ALT_GR("ALT"),
CTRL("CTRL"),
META("ACTION"),
SHIFT("SHIFT"),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 13ffd63..c6594ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.communalHub
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -44,6 +45,7 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ private val glanceableHubTransitions: GlanceableHubTransitions,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
@@ -57,6 +59,17 @@
listenForDreamingToGone()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
+ listenForDreamingToGlanceableHub()
+ }
+
+ private fun listenForDreamingToGlanceableHub() {
+ if (!communalHub()) return
+ glanceableHubTransitions.listenForGlanceableHubTransition(
+ transitionName = "listenForDreamingToGlanceableHub",
+ transitionOwnerName = TAG,
+ fromState = KeyguardState.DREAMING,
+ toState = KeyguardState.GLANCEABLE_HUB,
+ )
}
fun startToLockscreenTransition() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 71d941a..fbf195e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -20,7 +20,6 @@
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags
-import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -31,7 +30,7 @@
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleMultiple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -71,7 +70,11 @@
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
- duration = DEFAULT_DURATION.inWholeMilliseconds
+ duration =
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
@@ -80,10 +83,11 @@
* transition.
*/
private fun listenForHubToLockscreen() {
- glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ glanceableHubTransitions.listenForGlanceableHubTransition(
transitionName = "listenForHubToLockscreen",
transitionOwnerName = TAG,
- toScene = CommunalSceneKey.Blank,
+ fromState = KeyguardState.GLANCEABLE_HUB,
+ toState = KeyguardState.LOCKSCREEN,
)
}
@@ -175,7 +179,7 @@
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
- val DEFAULT_DURATION = 400.milliseconds
+ val DEFAULT_DURATION = 1.seconds
val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 57e9ac7..40b2c63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -19,7 +19,6 @@
import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
-import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -39,6 +38,7 @@
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -361,10 +361,11 @@
return
}
- glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ glanceableHubTransitions.listenForGlanceableHubTransition(
transitionName = "listenForLockscreenToGlanceableHub",
transitionOwnerName = TAG,
- toScene = CommunalSceneKey.Communal
+ fromState = KeyguardState.LOCKSCREEN,
+ toState = KeyguardState.GLANCEABLE_HUB,
)
}
@@ -380,6 +381,7 @@
KeyguardState.AOD -> TO_AOD_DURATION
KeyguardState.DOZING -> TO_DOZING_DURATION
KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -395,6 +397,6 @@
val TO_AOD_DURATION = 500.milliseconds
val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
val TO_GONE_DURATION = DEFAULT_DURATION
- val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = 1.seconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index ca66153..809c0aee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -52,20 +52,18 @@
* externally. The progress is used for both transitions caused by user touch input or by
* programmatic changes.
*/
- fun listenForLockscreenAndHubTransition(
+ fun listenForGlanceableHubTransition(
transitionName: String,
transitionOwnerName: String,
- toScene: CommunalSceneKey
+ fromState: KeyguardState,
+ toState: KeyguardState,
) {
- val fromState: KeyguardState
- val toState: KeyguardState
- if (toScene == CommunalSceneKey.Blank) {
- fromState = KeyguardState.GLANCEABLE_HUB
- toState = KeyguardState.LOCKSCREEN
- } else {
- fromState = KeyguardState.LOCKSCREEN
- toState = KeyguardState.GLANCEABLE_HUB
- }
+ val toScene =
+ if (toState == KeyguardState.GLANCEABLE_HUB) {
+ CommunalSceneKey.Communal
+ } else {
+ CommunalSceneKey.Blank
+ }
var transitionId: UUID? = null
scope.launch("$transitionOwnerName#$transitionName") {
communalInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 8b278cd..b8ba098 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -185,13 +185,16 @@
return getOrCreateFlow(edge)
.map { step ->
StateToValue(
- step.transitionState,
- when (step.transitionState) {
- STARTED -> stepToValue(step)
- RUNNING -> stepToValue(step)
- CANCELED -> onCancel?.invoke()
- FINISHED -> onFinish?.invoke()
- }
+ from = step.from,
+ to = step.to,
+ transitionState = step.transitionState,
+ value =
+ when (step.transitionState) {
+ STARTED -> stepToValue(step)
+ RUNNING -> stepToValue(step)
+ CANCELED -> onCancel?.invoke()
+ FINISHED -> onFinish?.invoke()
+ }
)
.also { logger.logTransitionStep(name, step, it.value) }
}
@@ -208,6 +211,10 @@
}
data class StateToValue(
+ val from: KeyguardState? = null,
+ val to: KeyguardState? = null,
val transitionState: TransitionState = TransitionState.FINISHED,
val value: Float? = 0f,
-)
+) {
+ fun isToOrFrom(state: KeyguardState) = from == state || to == state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index c58a03c..ed48848 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -43,6 +43,7 @@
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -101,6 +102,10 @@
val burnInLayerId = R.id.burn_in_layer
val aodNotificationIconContainerId = R.id.aod_notification_icon_container
val largeClockId = R.id.lockscreen_clock_view_large
+ val indicationArea = R.id.keyguard_indication_area
+ val startButton = R.id.start_button
+ val endButton = R.id.end_button
+ val lockIcon = R.id.lock_icon_view
if (keyguardBottomAreaRefactor()) {
view.setOnTouchListener { _, event ->
@@ -200,10 +205,29 @@
launch {
burnInParams
.flatMapLatest { params -> viewModel.translationX(params) }
- .collect { x ->
- childViews[burnInLayerId]?.translationX = x
- childViews[largeClockId]?.translationX = x
- childViews[aodNotificationIconContainerId]?.translationX = x
+ .collect { state ->
+ val px = state.value ?: return@collect
+ when {
+ state.isToOrFrom(KeyguardState.AOD) -> {
+ childViews[largeClockId]?.translationX = px
+ childViews[burnInLayerId]?.translationX = px
+ childViews[aodNotificationIconContainerId]
+ ?.translationX = px
+ }
+ state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+ for ((key, childView) in childViews.entries) {
+ when (key) {
+ indicationArea,
+ startButton,
+ endButton,
+ lockIcon -> {
+ // Do not move these views
+ }
+ else -> childView.translationX = px
+ }
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index f208e85..8a3b57b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -18,7 +18,9 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
@@ -38,6 +40,7 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
+ keyguardInteractor: KeyguardInteractor,
) {
/** The alpha level for the entire lockscreen while in AOD. */
@@ -46,7 +49,8 @@
keyguardTransitionInteractor.transitions,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) },
goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) },
- ) { step, goneToAodAlpha, goneToDozingAlpha ->
+ keyguardInteractor.keyguardAlpha.onStart { emit(1f) },
+ ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha ->
if (step.to == GONE) {
// When transitioning to GONE, only emit a value when complete as other
// transitions may be controlling the alpha fade
@@ -57,6 +61,8 @@
emit(goneToAodAlpha)
} else if (step.from == GONE && step.to == DOZING) {
emit(goneToDozingAlpha)
+ } else if (!migrateClocksToBlueprint()) {
+ emit(keyguardAlpha)
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..374a932
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class DreamingToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) {
+
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+
+ fun dreamOverlayTranslationX(translatePx: Int): Flow<Float> {
+ return transitionAnimation.sharedFlow(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ onStep = { it * -translatePx },
+ interpolator = EMPHASIZED,
+ name = "DREAMING->GLANCEABLE_HUB: overlayTranslationX",
+ )
+ }
+
+ val dreamOverlayAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 167.milliseconds,
+ onStep = { 1f - it },
+ name = "DREAMING->GLANCEABLE_HUB: dreamOverlayAlpha",
+ )
+
+ private companion object {
+ val TO_GLANCEABLE_HUB_DURATION = 1.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 6aa2eca..e5b5964 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -16,13 +16,22 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/**
* Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -33,32 +42,43 @@
class GlanceableHubToLockscreenTransitionViewModel
@Inject
constructor(
+ configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
- duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ duration = TO_LOCKSCREEN_DURATION,
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.LOCKSCREEN,
)
- // TODO(b/315205222): implement full animation spec instead of just a simple fade.
val keyguardAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
- onStep = { it },
- onFinish = { 1f },
- onCancel = { 0f },
- name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
- )
+ transitionAnimation
+ .sharedFlow(
+ duration = 167.milliseconds,
+ startTime = 167.milliseconds,
+ onStep = { it },
+ onFinish = { 1f },
+ onCancel = { 0f },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
+ )
+ .onStart { emit(0f) }
- // TODO(b/315205216): implement full animation spec instead of just a simple fade.
- val notificationAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
- onStep = { it },
- onFinish = { 1f },
- onCancel = { 0f },
- name = "GLANCEABLE_HUB->LOCKSCREEN: notificationAlpha",
- )
+ val keyguardTranslationX: Flow<StateToValue> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x)
+ .flatMapLatest { translatePx: Int ->
+ transitionAnimation.sharedFlowWithState(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ interpolator = EMPHASIZED,
+ onCancel = { -translatePx.toFloat() },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
+ )
+ }
+
+ val notificationAlpha: Flow<Float> = keyguardAlpha
+
+ val notificationTranslationX: Flow<Float> =
+ keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 188be24..4db942cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -20,7 +20,9 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -57,6 +59,7 @@
lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+ transitionInteractor: KeyguardTransitionInteractor,
) {
data class PreviewMode(
@@ -71,6 +74,24 @@
*/
private val previewMode = MutableStateFlow(PreviewMode())
+ private val showingLockscreen: Flow<Boolean> =
+ transitionInteractor.finishedKeyguardState.map { keyguardState ->
+ keyguardState == KeyguardState.LOCKSCREEN
+ }
+
+ /** The only time the expansion is important is while lockscreen is actively displayed */
+ private val shadeExpansionAlpha =
+ combine(
+ showingLockscreen,
+ shadeInteractor.anyExpansion,
+ ) { showingLockscreen, expansion ->
+ if (showingLockscreen) {
+ 1 - expansion
+ } else {
+ 0f
+ }
+ }
+
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
* wallpaper picker application. This is ignored for the actual, real lock screen experience.
@@ -101,7 +122,7 @@
lockscreenToGoneTransitionViewModel.shortcutsAlpha,
lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
- shadeInteractor.qsExpansion.map { 1 - it },
+ shadeExpansionAlpha,
)
/** The source of truth of alpha for all of the quick affordances on lockscreen */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index f790d35..3511933 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -32,6 +32,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -165,8 +166,12 @@
return aodBurnInViewModel.translationY(params)
}
- fun translationX(params: BurnInParameters): Flow<Float> {
- return aodBurnInViewModel.translationX(params)
+ fun translationX(params: BurnInParameters): Flow<StateToValue> {
+ return merge(
+ aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) },
+ lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
+ )
}
fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 3afa49e..978e71e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -16,13 +16,22 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/**
* Breaks down LOCKSCREEN->GLANCEABLE_HUB transition into discrete steps for corresponding views to
@@ -33,6 +42,7 @@
class LockscreenToGlanceableHubTransitionViewModel
@Inject
constructor(
+ configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
@@ -42,23 +52,35 @@
to = KeyguardState.GLANCEABLE_HUB,
)
- // TODO(b/315205222): implement full animation spec instead of just a simple fade.
val keyguardAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
- onStep = { 1f - it },
- onFinish = { 0f },
- onCancel = { 1f },
- name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
- )
+ transitionAnimation
+ .sharedFlow(
+ duration = 167.milliseconds,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
+ )
+ .onStart { emit(1f) }
- // TODO(b/315205216): implement full animation spec instead of just a simple fade.
- val notificationAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
- onStep = { 1f - it },
- onFinish = { 0f },
- onCancel = { 1f },
- name = "LOCKSCREEN->GLANCEABLE_HUB: notificationAlpha",
- )
+ val keyguardTranslationX: Flow<StateToValue> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x)
+ .flatMapLatest { translatePx: Int ->
+ transitionAnimation.sharedFlowWithState(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { value -> value * translatePx },
+ // Move notifications back to their original position since they can be
+ // accessed from the shade.
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED,
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX"
+ )
+ }
+
+ val notificationAlpha: Flow<Float> = keyguardAlpha
+
+ val notificationTranslationX: Flow<Float> =
+ keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 4f28b46..378ce52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -108,6 +108,7 @@
0f
}
},
+ onFinish = { 0f },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index e15e038..c629337 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -230,18 +230,12 @@
val currentAllowMediaPlayerOnLockScreen = allowMediaPlayerOnLockScreen
val useSplitShade = useSplitShade
val shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade()
-
visible =
isMediaHostVisible &&
isBypassNotEnabled &&
keyguardOrUserSwitcher &&
currentAllowMediaPlayerOnLockScreen &&
shouldBeVisibleForSplitShade
- if (visible) {
- showMediaPlayer()
- } else {
- hideMediaPlayer()
- }
logger.logRefreshMediaPosition(
reason = reason,
visible = visible,
@@ -251,8 +245,17 @@
mediaHostVisible = isMediaHostVisible,
bypassNotEnabled = isBypassNotEnabled,
currentAllowMediaPlayerOnLockScreen = currentAllowMediaPlayerOnLockScreen,
- shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade
+ shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade,
)
+ val currActiveContainer = activeContainer
+
+ logger.logActiveMediaContainer("before refreshMediaPosition", currActiveContainer)
+ if (visible) {
+ showMediaPlayer()
+ } else {
+ hideMediaPlayer()
+ }
+ logger.logActiveMediaContainer("after refreshMediaPosition", currActiveContainer)
lastUsedStatusBarState = currentState
}
@@ -293,9 +296,11 @@
}
private fun setVisibility(view: ViewGroup?, newVisibility: Int) {
- val previousVisibility = view?.visibility
- view?.visibility = newVisibility
- if (previousVisibility != newVisibility) {
+ val currentMediaContainer = view ?: return
+
+ val previousVisibility = currentMediaContainer.visibility
+ currentMediaContainer.visibility = newVisibility
+ if (previousVisibility != newVisibility && currentMediaContainer is MediaContainerView) {
visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
}
}
@@ -325,4 +330,7 @@
}
}
}
+
+ private val activeContainer: ViewGroup? =
+ if (useSplitShade) splitShadeContainer else singlePaneContainer
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
index 41fef88..0dd4b58 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.ui
+import android.view.ViewGroup
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.KeyguardMediaControllerLog
@@ -36,8 +37,8 @@
mediaHostVisible: Boolean,
bypassNotEnabled: Boolean,
currentAllowMediaPlayerOnLockScreen: Boolean,
- shouldBeVisibleForSplitShade: Boolean
- ) =
+ shouldBeVisibleForSplitShade: Boolean,
+ ) {
logBuffer.log(
TAG,
DEBUG,
@@ -63,6 +64,19 @@
"shouldBeVisibleForSplitShade=$str3)"
}
)
+ }
+
+ fun logActiveMediaContainer(reason: String, activeContainer: ViewGroup?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = reason
+ str2 = activeContainer.toString()
+ },
+ { "activeMediaContainerVisibility(reason=$str1, activeContainer=$str2)" }
+ )
+ }
private companion object {
private const val TAG = "KeyguardMediaControllerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index e97c9d3d..7cf18ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -18,6 +18,7 @@
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
+import static com.android.systemui.Flags.legacyLeAudioSharing;
import static com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import android.animation.Animator;
@@ -597,7 +598,9 @@
// Show the broadcast dialog button only when the le audio is enabled.
mShowBroadcastDialogButton =
- data.getDevice() != null && data.getDevice().getShowBroadcastButton();
+ legacyLeAudioSharing()
+ && data.getDevice() != null
+ && data.getDevice().getShowBroadcastButton();
bindOutputSwitcherAndBroadcastButton(mShowBroadcastDialogButton, data);
bindGutsMenuForPlayer(data);
bindPlayerContentDescription(data);
@@ -1930,3 +1933,4 @@
interactedSubcardCardinality);
}
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 2f5f925..c379d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static com.android.systemui.Flags.legacyLeAudioSharing;
+
import android.content.Context;
import android.os.Bundle;
import android.util.FeatureFlagUtils;
@@ -108,6 +110,7 @@
@Override
public boolean isBroadcastSupported() {
+ if (!legacyLeAudioSharing()) return false;
boolean isBluetoothLeDevice = false;
boolean isBroadcastEnabled = false;
if (FeatureFlagUtils.isEnabled(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
index cde3835..187b444 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -35,6 +35,7 @@
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
@@ -55,6 +56,7 @@
) : Dumpable {
private val initialized = AtomicBoolean(false)
+ private lateinit var currentTilesInteractor: CurrentTilesInteractor
/** Start collection of signals following the user from [currentTilesInteractor]. */
fun init(currentTilesInteractor: CurrentTilesInteractor) {
@@ -62,58 +64,74 @@
return
}
+ this.currentTilesInteractor = currentTilesInteractor
dumpManager.registerNormalDumpable(TAG, this)
scope.launch {
currentTilesInteractor.userId.collectLatest { userId ->
coroutineScope {
- val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
-
- autoAddables
- .map { addable ->
- val autoAddSignal = addable.autoAddSignal(userId)
- when (val lifecycle = addable.autoAddTracking) {
- is AutoAddTracking.Always -> autoAddSignal
- is AutoAddTracking.Disabled -> emptyFlow()
- is AutoAddTracking.IfNotAdded -> {
- if (lifecycle.spec !in previouslyAdded.value) {
- autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
- } else {
- emptyFlow()
- }
- }
- }
- }
- .merge()
- .collect { signal ->
- when (signal) {
- is AutoAddSignal.Add -> {
- if (signal.spec !in previouslyAdded.value) {
- currentTilesInteractor.addTile(signal.spec, signal.position)
- qsPipelineLogger.logTileAutoAdded(
- userId,
- signal.spec,
- signal.position
- )
- repository.markTileAdded(userId, signal.spec)
- }
- }
- is AutoAddSignal.Remove -> {
- currentTilesInteractor.removeTiles(setOf(signal.spec))
- qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
- repository.unmarkTileAdded(userId, signal.spec)
- }
- is AutoAddSignal.RemoveTracking -> {
- qsPipelineLogger.logTileUnmarked(userId, signal.spec)
- repository.unmarkTileAdded(userId, signal.spec)
- }
- }
- }
+ launch { collectAutoAddSignalsForUser(userId) }
+ launch { markTrackIfNotAddedTilesThatAreCurrent(userId) }
}
}
}
}
+ private suspend fun markTrackIfNotAddedTilesThatAreCurrent(userId: Int) {
+ val trackIfNotAddedSpecs =
+ autoAddables
+ .map { it.autoAddTracking }
+ .filterIsInstance<AutoAddTracking.IfNotAdded>()
+ .map { it.spec }
+ currentTilesInteractor.currentTiles
+ .map { tiles -> tiles.map { it.spec } }
+ .collect {
+ it.filter { it in trackIfNotAddedSpecs }
+ .forEach { spec -> repository.markTileAdded(userId, spec) }
+ }
+ }
+
+ private suspend fun CoroutineScope.collectAutoAddSignalsForUser(userId: Int) {
+ val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+
+ autoAddables
+ .map { addable ->
+ val autoAddSignal = addable.autoAddSignal(userId)
+ when (val lifecycle = addable.autoAddTracking) {
+ is AutoAddTracking.Always -> autoAddSignal
+ is AutoAddTracking.Disabled -> emptyFlow()
+ is AutoAddTracking.IfNotAdded -> {
+ if (lifecycle.spec !in previouslyAdded.value) {
+ autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
+ } else {
+ emptyFlow()
+ }
+ }
+ }
+ }
+ .merge()
+ .collect { signal ->
+ when (signal) {
+ is AutoAddSignal.Add -> {
+ if (signal.spec !in previouslyAdded.value) {
+ currentTilesInteractor.addTile(signal.spec, signal.position)
+ qsPipelineLogger.logTileAutoAdded(userId, signal.spec, signal.position)
+ repository.markTileAdded(userId, signal.spec)
+ }
+ }
+ is AutoAddSignal.Remove -> {
+ currentTilesInteractor.removeTiles(setOf(signal.spec))
+ qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
+ repository.unmarkTileAdded(userId, signal.spec)
+ }
+ is AutoAddSignal.RemoveTracking -> {
+ qsPipelineLogger.logTileUnmarked(userId, signal.spec)
+ repository.unmarkTileAdded(userId, signal.spec)
+ }
+ }
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
with(pw.asIndenting()) {
println("AutoAddables:")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
index cff95d8..1b3e585 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -68,8 +68,7 @@
serviceInteractor.setUser(user)
// Wait for the CustomTileInteractor to become initialized first, because
- // binding
- // the service might access it
+ // binding the service might access it
customTileInteractor.initForUser(user)
// Bind the TileService for not active tile
serviceInteractor.bindOnStart()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index fd96fc5..3e507cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -77,6 +77,13 @@
suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
+ * True if the tile is active and false the otherwise. This effectively is a value of the
+ * [android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE]. This is not the same as
+ * [Tile.STATE_ACTIVE].
+ */
+ suspend fun isTileActive(): Boolean = customTileRepository.isTileActive()
+
+ /**
* Initializes the repository for the current user. Suspends until it's safe to call [getTile]
* which needs at least one of the following:
* - defaults are loaded;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
index acff40f..79e903c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
@@ -58,7 +58,7 @@
private val activityStarter: ActivityStarter,
private val userActionInteractor: Lazy<CustomTileUserActionInteractor>,
private val customTileInteractor: CustomTileInteractor,
- private val userRepository: UserRepository,
+ userRepository: UserRepository,
private val qsTileLogger: QSTileLogger,
private val tileServices: TileServices,
@QSTileScope private val tileScope: CoroutineScope,
@@ -78,10 +78,10 @@
get() = tileReceivingInterface.mutableRefreshEvents
/** Clears all pending binding for an active tile and binds not active one. */
- fun bindOnStart() {
+ suspend fun bindOnStart() {
try {
with(getTileServiceManager()) {
- if (isActiveTile) {
+ if (customTileInteractor.isTileActive()) {
clearPendingBind()
} else {
setBindRequested(true)
@@ -94,10 +94,10 @@
}
/** Binds active tile WITHOUT CLEARING pending binds. */
- fun bindOnClick() {
+ suspend fun bindOnClick() {
try {
with(getTileServiceManager()) {
- if (isActiveTile) {
+ if (customTileInteractor.isTileActive()) {
setBindRequested(true)
tileServiceInterface.onStartListening()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
index c3e1fea..a16ac36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -77,7 +77,7 @@
qsTileLogger.logCustomTileUserActionDelivered(tileSpec)
}
- private fun click(
+ private suspend fun click(
view: View?,
activityLaunchForClick: PendingIntent?,
) {
@@ -114,9 +114,6 @@
}
fun startActivityAndCollapse(pendingIntent: PendingIntent) {
- if (!pendingIntent.isActivity) {
- return
- }
if (!isTokenGranted) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 99e91c1..e577178 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -511,6 +511,12 @@
return true;
}
}
+ } else if (migrateClocksToBlueprint()) {
+ // This final check handles swipes on HUNs and when Pulsing
+ if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
+ mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
+ return true;
+ }
}
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ca19f71..3908ede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -615,14 +615,7 @@
args.argi2 = state1;
args.argi3 = state2;
args.argi4 = animate ? 1 : 0;
- Message msg = mHandler.obtainMessage(MSG_DISABLE, args);
- if (Looper.myLooper() == mHandler.getLooper()) {
- // If its the right looper execute immediately so hides can be handled quickly.
- mHandler.handleMessage(msg);
- msg.recycle();
- } else {
- msg.sendToTarget();
- }
+ mHandler.obtainMessage(MSG_DISABLE, args).sendToTarget();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index e598242..a4741a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -25,8 +25,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -71,6 +74,7 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
@@ -114,6 +118,7 @@
private Button mButtonInput;
private Button mButtonOpenApps;
private Button mButtonSpecificApp;
+ private CharSequence mCurrentAppPackageName;
private TextView mNoSearchResults;
private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
@@ -412,8 +417,10 @@
mWindowManager.requestAppKeyboardShortcuts(result -> {
// Add specific app shortcuts
if (result.isEmpty()) {
+ mCurrentAppPackageName = null;
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
} else {
+ mCurrentAppPackageName = result.get(0).getPackageName();
mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
}
@@ -823,6 +830,7 @@
mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result);
mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView);
setButtonsDefaultStatus(keyboardShortcutsView);
+ populateCurrentAppButton();
populateKeyboardShortcutSearchList(shortcutsContainer);
// Workaround for solve issue about dialog not full expanded when landscape.
@@ -1272,6 +1280,41 @@
mFullButtonList.add(mButtonSpecificApp);
}
+ private void resetCurrentAppButton() {
+ if (mButtonSpecificApp == null) {
+ return;
+ }
+ mButtonSpecificApp.setText(
+ mContext.getString(R.string.keyboard_shortcut_search_category_current_app));
+ // TODO(b/325252986): Reset icon once the icon is implemented
+ }
+
+ private void populateCurrentAppButton() {
+ if (mButtonSpecificApp == null) {
+ return;
+ }
+ if (mCurrentAppPackageName != null) {
+ final int userId = mContext.getUserId();
+ try {
+ PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(
+ mContext,
+ userId);
+ ApplicationInfo appInfo = pmUser.getApplicationInfoAsUser(
+ mCurrentAppPackageName.toString(),
+ 0,
+ userId);
+ // According to the API, we will always get a label
+ mButtonSpecificApp.setText(pmUser.getApplicationLabel(appInfo));
+ // TODO(b/325252986): Show icon once it has been defined
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Package name not found", e);
+ resetCurrentAppButton();
+ }
+ } else {
+ resetCurrentAppButton();
+ }
+ }
+
private void setButtonFocusColor(int i, boolean isFocused) {
if (isFocused) {
mFullButtonList.get(i).setTextColor(getColorOfTextColorOnAccent());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 9916ef6..1a06eec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -156,14 +156,12 @@
final String action = intent.getAction();
if (ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED.equals(action)) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- mKeyguardAllowingNotifications =
- intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
- if (mCurrentUserId == getSendingUserId()) {
- boolean changed = updateLockscreenNotificationSetting();
- if (changed) {
- notifyNotificationStateChanged();
- }
+ mKeyguardAllowingNotifications =
+ intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
+ if (mCurrentUserId == getSendingUserId()) {
+ boolean changed = updateLockscreenNotificationSetting();
+ if (changed) {
+ notifyNotificationStateChanged();
}
}
}
@@ -176,36 +174,26 @@
final String action = intent.getAction();
if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- boolean changed = false;
- int sendingUserId = getSendingUserId();
- if (sendingUserId == USER_ALL) {
- // When a Device Owner triggers changes it's sent as USER_ALL. Normalize
- // the user before calling into DPM
- sendingUserId = mCurrentUserId;
- @SuppressLint("MissingPermission")
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = users.size() - 1; i >= 0; i--) {
- changed |= updateDpcSettings(users.get(i).id);
- }
- } else {
- changed |= updateDpcSettings(sendingUserId);
- }
-
- if (mCurrentUserId == sendingUserId) {
- changed |= updateLockscreenNotificationSetting();
- }
- if (changed) {
- notifyNotificationStateChanged();
+ boolean changed = false;
+ int sendingUserId = getSendingUserId();
+ if (sendingUserId == USER_ALL) {
+ // When a Device Owner triggers changes it's sent as USER_ALL. Normalize
+ // the user before calling into DPM
+ sendingUserId = mCurrentUserId;
+ @SuppressLint("MissingPermission")
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ changed |= updateDpcSettings(users.get(i).id);
}
} else {
- if (isCurrentProfile(getSendingUserId())) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- // TODO(b/231976036): Consolidate pipeline invalidations related to this
- // event
- // notifyNotificationStateChanged();
- }
+ changed |= updateDpcSettings(sendingUserId);
+ }
+
+ if (mCurrentUserId == sendingUserId) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
}
}
}
@@ -225,12 +213,10 @@
updateCurrentProfilesCache();
} else if (Objects.equals(action, Intent.ACTION_USER_ADDED)){
updateCurrentProfilesCache();
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
- mBackgroundExecutor.execute(() -> {
- initValuesForUser(userId);
- });
- }
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mBackgroundExecutor.execute(() -> {
+ initValuesForUser(userId);
+ });
} else if (profileAvailabilityActions(action)) {
updateCurrentProfilesCache();
} else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
@@ -360,28 +346,16 @@
}
private void init() {
- mLockscreenSettingsObserver = new ExecutorContentObserver(
- mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
- ? mBackgroundExecutor
- : mMainExecutor) {
+ mLockscreenSettingsObserver = new ExecutorContentObserver(mBackgroundExecutor) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- @SuppressLint("MissingPermission")
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = users.size() - 1; i >= 0; i--) {
- onChange(selfChange, uris, flags,users.get(i).getUserHandle());
- }
- } else {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
- // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
- mUsersAllowingPrivateNotifications.clear();
- mUsersAllowingNotifications.clear();
- // ... and refresh all the notifications
- updateLockscreenNotificationSetting();
- notifyNotificationStateChanged();
+ @SuppressLint("MissingPermission")
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ onChange(selfChange, uris, flags,users.get(i).getUserHandle());
}
+
}
// Note: even though this is an override, this method is not called by the OS
@@ -390,22 +364,20 @@
@Override
public void onChange(boolean selfChange, Collection<Uri> uris,
int flags, UserHandle user) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- boolean changed = false;
- for (Uri uri: uris) {
- if (SHOW_LOCKSCREEN.equals(uri)) {
- changed |= updateUserShowSettings(user.getIdentifier());
- } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
- changed |= updateUserShowPrivateSettings(user.getIdentifier());
- }
+ boolean changed = false;
+ for (Uri uri: uris) {
+ if (SHOW_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowSettings(user.getIdentifier());
+ } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowPrivateSettings(user.getIdentifier());
}
+ }
- if (mCurrentUserId == user.getIdentifier()) {
- changed |= updateLockscreenNotificationSetting();
- }
- if (changed) {
- notifyNotificationStateChanged();
- }
+ if (mCurrentUserId == user.getIdentifier()) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
}
}
};
@@ -432,16 +404,10 @@
mLockscreenSettingsObserver,
USER_ALL);
- if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
- mSettingsObserver);
- }
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
- ? mBackgroundExecutor : null, UserHandle.ALL);
+ mBackgroundExecutor, UserHandle.ALL);
if (keyguardPrivateNotifications()) {
mBroadcastDispatcher.registerReceiver(mKeyguardReceiver,
new IntentFilter(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED),
@@ -471,17 +437,13 @@
mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- // Set up
- mBackgroundExecutor.execute(() -> {
- @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
- for (int i = users.size() - 1; i >= 0; i--) {
- initValuesForUser(users.get(i).id);
- }
- });
- } else {
- mSettingsObserver.onChange(false); // set up
- }
+ // Set up
+ mBackgroundExecutor.execute(() -> {
+ @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ initValuesForUser(users.get(i).id);
+ }
+ });
}
private void initValuesForUser(@UserIdInt int userId) {
@@ -519,26 +481,15 @@
boolean show;
boolean allowedByDpm;
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- if (keyguardPrivateNotifications()) {
- show = mUsersUsersAllowingNotifications.get(mCurrentUserId);
- } else {
- show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
- && mKeyguardAllowingNotifications;
- }
- // If DPC never notified us about a user, that means they have no policy for the user,
- // and they allow the behavior
- allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+ if (keyguardPrivateNotifications()) {
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId);
} else {
- show = mSecureSettings.getIntForUser(
- LOCK_SCREEN_SHOW_NOTIFICATIONS,
- 1,
- mCurrentUserId) != 0;
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
- null /* admin */, mCurrentUserId);
- allowedByDpm = (dpmFlags
- & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+ && mKeyguardAllowingNotifications;
}
+ // If DPC never notified us about a user, that means they have no policy for the user,
+ // and they allow the behavior
+ allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
final boolean oldValue = mShowLockscreenNotifications;
setShowLockscreenNotifications(show && allowedByDpm);
@@ -600,42 +551,24 @@
* when the lockscreen is in "public" (secure & locked) mode?
*/
public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- if (userHandle == USER_ALL) {
- userHandle = mCurrentUserId;
- }
- if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- Log.i(TAG, "Asking for redact notifs setting too early", new Throwable());
- return false;
- }
- if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
- return false;
- }
- if (keyguardPrivateNotifications()) {
- return mUsersUsersAllowingPrivateNotifications.get(userHandle)
- && mUsersDpcAllowingPrivateNotifications.get(userHandle)
- && mKeyguardAllowingNotifications;
- } else {
- return mUsersUsersAllowingPrivateNotifications.get(userHandle)
- && mUsersDpcAllowingPrivateNotifications.get(userHandle);
- }
+ if (userHandle == USER_ALL) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ Log.i(TAG, "Asking for redact notifs setting too early", new Throwable());
+ return false;
+ }
+ if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+ return false;
+ }
+ if (keyguardPrivateNotifications()) {
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
} else {
- if (userHandle == USER_ALL) {
- return true;
- }
-
- if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- final boolean allowed = allowedByUser && allowedByDpm;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed);
- return allowed;
- }
-
- return mUsersAllowingPrivateNotifications.get(userHandle);
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle);
}
}
@@ -688,48 +621,30 @@
* "public" (secure & locked) mode?
*/
public boolean userAllowsNotificationsInPublic(int userHandle) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- // Unlike 'show private', settings does not show a copy of this setting for each
- // profile, so it inherits from the parent user.
- if (userHandle == USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
- userHandle = mCurrentUserId;
- }
- if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
- // default value before moving to 'released'
- Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
- updateUserShowSettings(userHandle);
- }
- if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
- // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
- // default value before moving to 'released'
- Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
- updateDpcSettings(userHandle);
- }
- if (keyguardPrivateNotifications()) {
- return mUsersUsersAllowingNotifications.get(userHandle)
- && mUsersDpcAllowingNotifications.get(userHandle);
- } else {
- return mUsersUsersAllowingNotifications.get(userHandle)
- && mUsersDpcAllowingNotifications.get(userHandle)
- && mKeyguardAllowingNotifications;
- }
+ // Unlike 'show private', settings does not show a copy of this setting for each
+ // profile, so it inherits from the parent user.
+ if (userHandle == USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+ updateUserShowSettings(userHandle);
+ }
+ if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+ updateDpcSettings(userHandle);
+ }
+ if (keyguardPrivateNotifications()) {
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle);
} else {
- if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
- return true;
- }
-
- if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
- final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
- mUsersAllowingNotifications.append(userHandle, allowed);
- return allowed;
- }
- return mUsersAllowingNotifications.get(userHandle);
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
}
}
@@ -766,13 +681,7 @@
return true;
}
NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- return entry != null && entry.isChannelVisibilityPrivate();
- } else {
- return entry != null
- && entry.getRanking().getLockscreenVisibilityOverride()
- == Notification.VISIBILITY_PRIVATE;
- }
+ return entry != null && entry.isChannelVisibilityPrivate();
}
@SuppressLint("MissingPermission")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 6429815..11636bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.data.repository
import android.graphics.Rect
+import android.view.InsetsFlags
+import android.view.ViewDebug
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
@@ -305,8 +307,8 @@
letterboxDetails.isNotEmpty()
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("originalStatusBarAttributes: ${_originalStatusBarAttributes.value}")
- pw.println("modifiedStatusBarAttributes: ${modifiedStatusBarAttributes.value}")
+ pw.println("${_originalStatusBarAttributes.value}")
+ pw.println("${modifiedStatusBarAttributes.value}")
pw.println("statusBarMode: ${statusBarMode.value}")
}
@@ -320,7 +322,20 @@
val navbarColorManagedByIme: Boolean,
@WindowInsets.Type.InsetsType val requestedVisibleTypes: Int,
val letterboxDetails: List<LetterboxDetails>,
- )
+ ) {
+ override fun toString(): String {
+ return """
+ StatusBarAttributes(
+ appearance=${appearance.toAppearanceString()},
+ appearanceRegions=$appearanceRegions,
+ navbarColorManagedByIme=$navbarColorManagedByIme,
+ requestedVisibleTypes=${requestedVisibleTypes.toWindowInsetsString()},
+ letterboxDetails=$letterboxDetails
+ )
+ """
+ .trimIndent()
+ }
+ }
/**
* Internal class keeping track of how [StatusBarAttributes] were transformed into new
@@ -331,9 +346,31 @@
val appearanceRegions: List<AppearanceRegion>,
val navbarColorManagedByIme: Boolean,
val statusBarBounds: BoundsPair,
- )
+ ) {
+ override fun toString(): String {
+ return """
+ ModifiedStatusBarAttributes(
+ appearance=${appearance.toAppearanceString()},
+ appearanceRegions=$appearanceRegions,
+ navbarColorManagedByIme=$navbarColorManagedByIme,
+ statusBarBounds=$statusBarBounds
+ )
+ """
+ .trimIndent()
+ }
+ }
}
+private fun @receiver:WindowInsets.Type.InsetsType Int.toWindowInsetsString() =
+ "[${WindowInsets.Type.toString(this).replace(" ", ", ")}]"
+
+private fun @receiver:Appearance Int.toAppearanceString() =
+ if (this == 0) {
+ "NONE"
+ } else {
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
+ }
+
@AssistedFactory
interface StatusBarModePerDisplayRepositoryFactory {
fun create(@Assisted("displayId") displayId: Int): StatusBarModePerDisplayRepositoryImpl
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt
index c8f996a..c416d43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt
@@ -60,7 +60,7 @@
val didAppend = frames.lastOrNull()?.tryAddTrigger(event) == true
if (!didAppend) {
frames.add(Frame(event))
- if (frames.size > maxFrames) frames.removeFirst()
+ if (frames.size > maxFrames) frames.removeAt(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 9fb453a..65ab4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -136,7 +136,7 @@
}
launch {
- viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ viewModel.manageOrHistoryButton.accessibilityDescriptionId.collect { textId ->
footer.setManageOrHistoryButtonDescription(textId)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index d7fe36f..332ece4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -219,15 +219,11 @@
}
private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean {
- return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
- // info, and NotificationLockscreenUserManagerImpl is already listening for updates
- // to those
- entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
+ // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
+ // info, and NotificationLockscreenUserManagerImpl is already listening for updates
+ // to those
+ return entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
VISIBILITY_SECRET
- } else {
- entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
- }
}
override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d2ff266..715505d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1039,6 +1039,11 @@
mView.setTranslationY(translationY);
}
+ /** Set view x-translation */
+ public void setTranslationX(float translationX) {
+ mView.setTranslationX(translationX);
+ }
+
public int indexOfChild(View view) {
return mView.indexOfChild(view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
index 4897b42..534e5c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.view
+import android.os.Trace
import android.service.notification.NotificationListenerService
import androidx.annotation.VisibleForTesting
import com.android.internal.statusbar.IStatusBarService
@@ -182,6 +183,8 @@
maybeLogVisibilityChanges(newlyVisible, noLongerVisible, activeNotifCount)
updateExpansionStates(newlyVisible, noLongerVisible)
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", activeNotifCount)
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]", newVisibilities.size)
lastLoggedVisibilities.clear()
lastLoggedVisibilities.putAll(newVisibilities)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index daea8af..77e146b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -144,6 +144,8 @@
.collect { y -> controller.setTranslationY(y) }
}
+ launch { viewModel.translationX.collect { x -> controller.translationX = x } }
+
if (!sceneContainerFlags.isEnabled()) {
launch {
viewModel.expansionAlpha(viewState).collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f325157..6b949a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -363,14 +363,9 @@
lockscreenToGlanceableHubRunning,
glanceableHubToLockscreenRunning,
merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- .onStart {
- // Transition flows don't emit a value on start, kick things off so the
- // combine starts.
- emit(1f)
- }
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
if (isOnGlanceableHubWithoutShade) {
// Notifications should not be visible on the glanceable hub.
@@ -409,6 +404,16 @@
}
/**
+ * The container may need to be translated in the x direction as the keyguard fades out, such as
+ * when swiping open the glanceable hub from the lockscreen.
+ */
+ val translationX: Flow<Float> =
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+ )
+
+ /**
* When on keyguard, there is limited space to display notifications so calculate how many could
* be shown. Otherwise, there is no limit since the vertical space will be scrollable.
*
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index 4d3243a..edb910a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -22,8 +22,10 @@
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
+import android.os.Handler;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
@@ -57,17 +59,22 @@
private ActivityStarter mActivityStarter;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private DumpManager mDumpManager = new DumpManager();
-
+ private Handler mHandler;
+ private Handler mBgHandler;
private KeyguardSliceViewController mController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ TestableLooper testableLooper = TestableLooper.get(this);
+ assert testableLooper != null;
+ mHandler = new Handler(testableLooper.getLooper());
+ mBgHandler = new Handler(testableLooper.getLooper());
when(mView.isAttachedToWindow()).thenReturn(true);
when(mView.getContext()).thenReturn(mContext);
- mController = new KeyguardSliceViewController(
- mView, mActivityStarter, mConfigurationController,
- mTunerService, mDumpManager, mDisplayTracker);
+ mController = new KeyguardSliceViewController(mHandler, mBgHandler, mView,
+ mActivityStarter, mConfigurationController, mTunerService, mDumpManager,
+ mDisplayTracker);
mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 569e064..8690d4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -99,6 +99,16 @@
}
}
+ @Test
+ fun useCredentialOwnerWhenParentProfileIsNull() {
+ val value = 1
+
+ whenever(userManager.getProfileParent(eq(USER_ID))).thenReturn(null)
+ whenever(userManager.getCredentialOwnerProfile(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.getParentProfileIdOrSelfId(USER_ID)).isEqualTo(value)
+ }
+
@Test fun pinCredentialWhenGood() = pinCredential(goodCredential())
@Test fun pinCredentialWhenBad() = pinCredential(badCredential())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 5b93df5..a5d577dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -57,7 +57,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -148,6 +147,7 @@
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
)
.apply { start() }
@@ -1372,6 +1372,44 @@
}
@Test
+ fun dreamingToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreaming(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+ runCurrent()
+
+ // WHEN a transition to the glanceable hub starts
+ val currentScene = CommunalSceneKey.Blank
+ val targetScene = CommunalSceneKey.Communal
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromDreamingTransitionInteractor::class.simpleName,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 0e9197e..f0607f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -22,7 +22,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
@@ -51,8 +52,8 @@
underTest =
animationFlow.setup(
duration = 1000.milliseconds,
- from = KeyguardState.GONE,
- to = KeyguardState.DREAMING,
+ from = GONE,
+ to = DREAMING,
)
}
@@ -192,17 +193,65 @@
runCurrent()
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.STARTED, 0f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.STARTED,
+ value = 0f
+ )
+ )
repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 0.6f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 0.6f
+ )
+ )
repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.2f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 1.2f
+ )
+ )
repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.6f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 1.6f
+ )
+ )
repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 2f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 2f
+ )
+ )
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.FINISHED, null))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.FINISHED,
+ value = null
+ )
+ )
}
@Test
@@ -251,8 +300,8 @@
state: TransitionState = TransitionState.RUNNING
): TransitionStep {
return TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.DREAMING,
+ from = GONE,
+ to = DREAMING,
value = value,
transitionState = state,
ownerName = "GoneToDreamingTransitionViewModelTest"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index bfa8433..716c40d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -59,7 +59,14 @@
// The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertThat(enterFromTopTranslationY)
- .isEqualTo(StateToValue(TransitionState.STARTED, pixels))
+ .isEqualTo(
+ StateToValue(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ value = pixels
+ )
+ )
repository.sendTransitionStep(step(.55f))
assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f))
@@ -70,7 +77,14 @@
// At the end, the translation should be complete and set to zero
repository.sendTransitionStep(step(1f))
assertThat(enterFromTopTranslationY)
- .isEqualTo(StateToValue(TransitionState.RUNNING, 0f))
+ .isEqualTo(
+ StateToValue(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.RUNNING,
+ value = 0f
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 18a34ba..1f14afa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -75,6 +77,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -130,6 +133,8 @@
@Mock
private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel
+ @Mock
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
@@ -146,6 +151,8 @@
// the viewModel does a `map { 1 - it }` on this value, which is why it's different
private val intendedShadeAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(0f)
+ private val intendedFinishedKeyguardStateFlow = MutableStateFlow(KeyguardState.LOCKSCREEN)
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -242,6 +249,7 @@
intendedAlphaMutableStateFlow.value = 1f
intendedShadeAlphaMutableStateFlow.value = 0f
+ intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha)
.thenReturn(intendedAlphaMutableStateFlow)
whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
@@ -263,7 +271,9 @@
whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
- whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+ whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+ whenever(transitionInteractor.finishedKeyguardState)
+ .thenReturn(intendedFinishedKeyguardStateFlow)
underTest =
KeyguardQuickAffordancesCombinedViewModel(
@@ -304,7 +314,8 @@
lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
- lockscreenToPrimaryBouncerTransitionViewModel
+ lockscreenToPrimaryBouncerTransitionViewModel,
+ transitionInteractor = transitionInteractor,
)
}
@@ -682,6 +693,30 @@
)
}
+ @Test
+ fun shadeExpansionAlpha_changes_whenOnLockscreen() =
+ testScope.runTest {
+ intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
+ intendedShadeAlphaMutableStateFlow.value = 0.25f
+ val underTest = collectLastValue(underTest.transitionAlpha)
+ assertEquals(0.75f, underTest())
+
+ intendedShadeAlphaMutableStateFlow.value = 0.3f
+ assertEquals(0.7f, underTest())
+ }
+
+ @Test
+ fun shadeExpansionAlpha_alwaysZero_whenNotOnLockscreen() =
+ testScope.runTest {
+ intendedFinishedKeyguardStateFlow.value = KeyguardState.GONE
+ intendedShadeAlphaMutableStateFlow.value = 0.5f
+ val underTest = collectLastValue(underTest.transitionAlpha)
+ assertEquals(0f, underTest())
+
+ intendedShadeAlphaMutableStateFlow.value = 0.25f
+ assertEquals(0f, underTest())
+ }
+
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index c896486..93d6121 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -41,6 +41,8 @@
import android.media.session.PlaybackState
import android.os.Bundle
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.AndroidTestingRunner
@@ -140,6 +142,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaControlPanelTest : SysuiTestCase() {
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private lateinit var player: MediaControlPanel
@@ -1244,6 +1247,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
fun bindBroadcastButton() {
initMediaViewHolderMocks()
initDeviceMediaData(true, APP_NAME)
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 2b62f03..d9ddc8e 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
@@ -35,6 +35,9 @@
import android.media.session.PlaybackState;
import android.os.PowerExemptionManager;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.FeatureFlagUtils;
@@ -61,6 +64,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -76,6 +80,9 @@
private static final String TEST_PACKAGE = "test_package";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
// Mock
private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
private MediaController mMediaController = mock(MediaController.class);
@@ -170,6 +177,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonVisibility_remoteBLEDevice_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -181,6 +189,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonVisibility_remoteNonBLEDevice_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -201,6 +210,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOnAndConnectBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -213,6 +223,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOnAndNoBleDevice_returnsFalse() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -225,6 +236,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_notSupportBroadcastAndflagOn_returnsFalse() {
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
@@ -233,6 +245,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOffAndConnectToBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -245,6 +258,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOffAndNoBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -257,6 +271,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -269,6 +284,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -281,6 +297,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -292,6 +309,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_noBroadcasting_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -303,6 +321,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_remoteNonLeDevice_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -355,6 +374,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
String stopText = mContext.getText(R.string.media_output_broadcast).toString();
MediaDevice mMediaDevice = mock(MediaDevice.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index c226790..b426d1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -450,7 +450,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- // THEN touch should NOT be intercepted by NotificationShade
+ // THEN touch should be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
}
@@ -469,7 +469,35 @@
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- // THEN touch should NOT be intercepted by NotificationShade
+ // THEN touch should be intercepted by NotificationShade
+ assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
+ }
+
+ @Test
+ fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() {
+ // GIVEN dozing
+ whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
+ // AND pulsing
+ whenever(dozeServiceHost.isPulsing()).thenReturn(true)
+ // AND status bar doesn't want it
+ whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
+ .thenReturn(false)
+ // AND shade is not fully expanded
+ whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+ // AND the lock icon does NOT want the touch
+ whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
+ // AND quick settings controller DOES want it
+ whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
+ .thenReturn(true)
+ // AND bouncer is not showing
+ whenever(centralSurfaces.isBouncerShowing()).thenReturn(false)
+ // AND panel view controller wants it
+ whenever(notificationPanelViewController.handleExternalInterceptTouch(DOWN_EVENT))
+ .thenReturn(true)
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+ // THEN touch should be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 3811f04..06410cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -431,28 +431,6 @@
@Test
public void publicMode_settingsDisallow() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in public mode and settings are configured to disallow
- // notifications in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
- when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
- .thenReturn(false);
-
- mEntry.setRanking(new RankingBuilder()
- .setChannel(new NotificationChannel("1", "1", 4))
- .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
- .setKey(mEntry.getKey()).build());
-
- // THEN filter out the entry
- assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
- }
-
- @Test
- public void publicMode_settingsDisallow_mainThread() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -473,7 +451,6 @@
@Test
public void publicMode_nullChannel_allowed() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -490,7 +467,6 @@
@Test
public void publicMode_notifDisallowed() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
channel.setLockscreenVisibility(VISIBILITY_SECRET);
// GIVEN an 'unfiltered-keyguard-showing' state
@@ -509,23 +485,6 @@
}
@Test
- public void publicMode_notifDisallowed_mainThread() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in public mode and settings are configured to disallow
- // notifications in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
- .setVisibilityOverride(VISIBILITY_SECRET).build());
-
- // THEN filter out the entry
- assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
- }
-
- @Test
public void doesNotExceedThresholdToShow() {
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -579,7 +538,6 @@
@Test
public void notificationChannelVisibilityNoOverride() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN a VISIBILITY_PRIVATE notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID));
@@ -602,7 +560,6 @@
@Test
public void notificationChannelVisibilitySecret() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN a VISIBILITY_PRIVATE notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index b9b8722..3e0082c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -274,8 +275,7 @@
)
)
runCurrent()
- // Expected alpha is inverse of progress as notifications are fading away
- assertThat(alpha).isEqualTo(1 - progress)
+ assertThat(alpha).isIn(Range.closed(0f, 1f))
// Finish transition to glanceable hub
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
index b88f302..1a9f4b4 100644
--- a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
+++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
@@ -24,12 +24,27 @@
* Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the
* same instance
*/
-class TestStubDrawable : Drawable() {
+class TestStubDrawable(private val name: String? = null) : Drawable() {
override fun draw(canvas: Canvas) = Unit
override fun setAlpha(alpha: Int) = Unit
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
override fun getOpacity(): Int = PixelFormat.UNKNOWN
- override fun equals(other: Any?): Boolean = this === other
+ override fun toString(): String {
+ return name ?: super.toString()
+ }
+
+ override fun getConstantState(): ConstantState =
+ TestStubConstantState(this, changingConfigurations)
+
+ private class TestStubConstantState(
+ private val drawable: Drawable,
+ private val changingConfigurations: Int,
+ ) : ConstantState() {
+
+ override fun newDrawable(): Drawable = drawable
+
+ override fun getChangingConfigurations(): Int = changingConfigurations
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e20a0ab..a9a2d91 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -43,7 +43,7 @@
class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository {
private val _transitions =
- MutableSharedFlow<TransitionStep>(replay = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
init {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
index 9fb3284..f1784a8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -28,5 +29,6 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+ keyguardInteractor = keyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
similarity index 60%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index f8ce707..b370859 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.qs.external
+package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-/** Returns mocks */
-var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
- Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } }
+val Kosmos.dreamingToGlanceableHubTransitionViewModel by
+ Kosmos.Fixture {
+ DreamingToGlanceableHubTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
index 28fce77..b1c21b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,6 +27,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture {
GlanceableHubToLockscreenTransitionViewModel(
+ configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
index 9fe4ea3..471381f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
@@ -18,13 +18,16 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture {
LockscreenToGlanceableHubTransitionViewModel(
+ configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
new file mode 100644
index 0000000..cff5980
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.qs.external
+
+import android.os.Binder
+import android.os.IBinder
+import android.service.quicksettings.IQSTileService
+
+class FakeIQSTileService : IQSTileService {
+
+ var isTileAdded: Boolean = false
+ private set
+ var isTileListening: Boolean = false
+ private set
+ var isUnlockComplete: Boolean = false
+ val clicks: List<IBinder?>
+ get() = mutableClicks
+
+ private val mutableClicks: MutableList<IBinder?> = mutableListOf()
+ private val binder = Binder()
+
+ override fun asBinder(): IBinder = binder
+
+ override fun onTileAdded() {
+ isTileAdded = true
+ }
+
+ override fun onTileRemoved() {
+ isTileAdded = false
+ }
+
+ override fun onStartListening() {
+ isTileListening = true
+ }
+
+ override fun onStopListening() {
+ isTileListening = false
+ }
+
+ override fun onClick(wtoken: IBinder?) {
+ mutableClicks.add(wtoken)
+ }
+
+ override fun onUnlockComplete() {
+ isUnlockComplete = true
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt
new file mode 100644
index 0000000..101335f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.external
+
+import android.service.quicksettings.IQSTileService
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+// TODO(b/299909989) Make a fake instead
+class FakeTileServiceManagerFacade(
+ private val iQSTileService: IQSTileService,
+ val tileServiceManager: TileServiceManager = mock {},
+) {
+
+ private var hasPendingBind: Boolean = false
+
+ var isBound: Boolean = false
+ private set
+
+ init {
+ with(tileServiceManager) {
+ whenever(tileService).thenReturn(iQSTileService)
+ whenever(setBindRequested(any())).then {
+ val isRequested: Boolean = it.getArgument(0)
+ hasPendingBind = isRequested
+ if (!isRequested) {
+ isBound = false
+ }
+ Unit
+ }
+ whenever(clearPendingBind()).then {
+ hasPendingBind = false
+ Unit
+ }
+ whenever(hasPendingBind()).then { hasPendingBind }
+ }
+ }
+
+ fun processPendingBind() {
+ if (hasPendingBind) {
+ isBound = true
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt
new file mode 100644
index 0000000..0975e55
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.qs.external
+
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeTileServicesFacade(
+ private val TileServiceManager: TileServiceManager,
+ val tileServices: TileServices = mock {}
+) {
+
+ var customTileInterface: CustomTileInterface? = null
+ private set
+
+ init {
+ with(tileServices) {
+ whenever(getTileWrapper(any())).then {
+ customTileInterface = it.getArgument(0)
+ TileServiceManager
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
new file mode 100644
index 0000000..36c2c2b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.qs.external
+
+import android.content.ComponentName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.componentName: ComponentName by Kosmos.Fixture()
+
+/** Returns mocks */
+var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} }
+
+val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() }
+val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by
+ Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) }
+
+val Kosmos.tileServiceManager: TileServiceManager by
+ Kosmos.Fixture { tileServiceManagerFacade.tileServiceManager }
+
+val Kosmos.tileServicesFacade: FakeTileServicesFacade by
+ Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) }
+val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 14f28fe..561e254 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -17,19 +17,47 @@
package com.android.systemui.qs.tiles.impl.custom
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.tileServices
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileUserActionInteractor
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.mock
var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileQsTileConfig: QSTileConfig by
+ Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
+
val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
Kosmos.Fixture { FakeCustomTileStatePersister() }
+val Kosmos.customTileInteractor: CustomTileInteractor by
+ Kosmos.Fixture {
+ CustomTileInteractor(
+ tileSpec,
+ customTileDefaultsRepository,
+ customTileRepository,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
@@ -48,3 +76,31 @@
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+
+val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
+ Kosmos.Fixture {
+ CustomTileServiceInteractor(
+ tileSpec,
+ activityStarter,
+ { customTileUserActionInteractor },
+ customTileInteractor,
+ userRepository,
+ qsTileLogger,
+ tileServices,
+ testScope.backgroundScope,
+ )
+ }
+
+val Kosmos.customTileUserActionInteractor: CustomTileUserActionInteractor by
+ Kosmos.Fixture {
+ CustomTileUserActionInteractor(
+ testCase.context,
+ tileSpec,
+ qsTileLogger,
+ mock {},
+ mock {},
+ FakeQSTileIntentUserInputHandler(),
+ testDispatcher,
+ customTileServiceInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 9d0faca..4f5c9b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -70,7 +70,7 @@
}
/** Shortcut for `Truth.assertAbout(states()).that(state)`. */
- fun assertThat(state: QSTileState?): QSTileStateSubject =
- Truth.assertAbout(states()).that(state)
+ fun assertThat(actual: QSTileState?): QSTileStateSubject =
+ Truth.assertAbout(states()).that(actual)
}
}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 053ed77..3ecdf3f 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -36,9 +36,11 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -57,6 +59,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
@@ -78,6 +81,7 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -87,6 +91,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
@RunWith(AndroidJUnit4.class)
@@ -815,6 +820,48 @@
WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
}
+ @Test
+ public void testOnRestore_noCropHints() throws Exception {
+ testParseCropHints(Map.of());
+ }
+
+ @Test
+ public void testOnRestore_singleCropHint() throws Exception {
+ Map<Integer, Rect> testMap = Map.of(WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4));
+ testParseCropHints(testMap);
+ }
+
+ @Test
+ public void testOnRestore_multipleCropHints() throws Exception {
+ Map<Integer, Rect> testMap = Map.of(
+ WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4),
+ WallpaperManager.SQUARE_PORTRAIT, new Rect(5, 6, 7, 8),
+ WallpaperManager.SQUARE_LANDSCAPE, new Rect(9, 10, 11, 12));
+ testParseCropHints(testMap);
+ }
+
+ private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
+ assumeTrue(multiCrop());
+ mockRestoredStaticWallpaperFile(testMap);
+ mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
+ mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+ BackupAnnotations.OperationType.RESTORE);
+
+ mWallpaperBackupAgent.onRestoreFinished();
+
+ ArgumentMatcher<SparseArray<Rect>> matcher = array -> {
+ boolean result = testMap.entrySet().stream().allMatch(entry -> {
+ int key = entry.getKey();
+ return (array.contains(key) && array.get(key).equals(testMap.get(key)));
+ });
+ for (int i = 0; i < array.size(); i++) {
+ if (!testMap.containsKey(array.keyAt(i))) result = false;
+ }
+ return result;
+ };
+ verify(mWallpaperManager).setStreamWithCrops(any(), argThat(matcher), eq(true), anyInt());
+ }
+
private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) {
when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId);
when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId);
@@ -880,6 +927,34 @@
fstream.close();
}
+ private void mockRestoredStaticWallpaperFile(Map<Integer, Rect> crops) throws Exception {
+ File wallpaperFile = new File(mContext.getFilesDir(), WALLPAPER_INFO_STAGE);
+ wallpaperFile.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(wallpaperFile, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "wp");
+ for (Map.Entry<Integer, Rect> entry: crops.entrySet()) {
+ String orientation = switch (entry.getKey()) {
+ case WallpaperManager.PORTRAIT -> "Portrait";
+ case WallpaperManager.LANDSCAPE -> "Landscape";
+ case WallpaperManager.SQUARE_PORTRAIT -> "SquarePortrait";
+ case WallpaperManager.SQUARE_LANDSCAPE -> "SquareLandscape";
+ default -> throw new IllegalArgumentException("Invalid orientation");
+ };
+ Rect rect = entry.getValue();
+ out.attributeInt(null, "cropLeft" + orientation, rect.left);
+ out.attributeInt(null, "cropTop" + orientation, rect.top);
+ out.attributeInt(null, "cropRight" + orientation, rect.right);
+ out.attributeInt(null, "cropBottom" + orientation, rect.bottom);
+ }
+ out.endTag(null, "wp");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+ }
+
private WallpaperInfo getFakeWallpaperInfo() throws Exception {
Context context = InstrumentationRegistry.getTargetContext();
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 132804f..53897e1 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -74,11 +74,14 @@
"androidx.test.monitor-for-device",
],
libs: [
+ "android.test.mock",
"framework-minus-apex.ravenwood",
+ "services.core.ravenwood",
"junit",
],
sdk_version: "core_current",
visibility: ["//frameworks/base"],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
}
// Carefully compiles against only test_current to support tests that
@@ -111,3 +114,9 @@
"androidx.test.monitor",
],
}
+
+filegroup {
+ name: "ravenwood-services-jarjar-rules",
+ srcs: ["ravenwood-services-jarjar-rules.txt"],
+ visibility: ["//frameworks/base"],
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
index a920f63..83a7b6e 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
@@ -32,4 +32,14 @@
@Target({METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface RavenwoodReplace {
+ /**
+ * One or more classes that aren't yet supported by Ravenwood, which is why this method is
+ * being replaced.
+ */
+ Class<?>[] blockedBy() default {};
+
+ /**
+ * General free-form description of why this method is being replaced.
+ */
+ String reason() default "";
}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 49cef07..6b67364 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -52,5 +52,6 @@
method <init> ()V stub
class android.content.Context stub
method <init> ()V stub
+ method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
class android.content.pm.PackageManager stub
method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
new file mode 100644
index 0000000..3668b03
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import android.content.Context;
+import android.hardware.ISerialManager;
+import android.hardware.SerialManager;
+import android.os.PermissionEnforcer;
+import android.os.ServiceManager;
+import android.test.mock.MockContext;
+import android.util.ArrayMap;
+import android.util.Singleton;
+
+import java.util.function.Supplier;
+
+public class RavenwoodContext extends MockContext {
+ private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer();
+
+ private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
+ private final ArrayMap<String, Supplier<?>> mNameToFactory = new ArrayMap<>();
+
+ private void registerService(Class<?> serviceClass, String serviceName,
+ Supplier<?> serviceSupplier) {
+ mClassToName.put(serviceClass, serviceName);
+ mNameToFactory.put(serviceName, serviceSupplier);
+ }
+
+ public RavenwoodContext() {
+ registerService(PermissionEnforcer.class,
+ Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer);
+ registerService(SerialManager.class,
+ Context.SERIAL_SERVICE, asSingleton(() ->
+ new SerialManager(this, ISerialManager.Stub.asInterface(
+ ServiceManager.getService(Context.SERIAL_SERVICE)))
+ ));
+ }
+
+ @Override
+ public Object getSystemService(String serviceName) {
+ // TODO: pivot to using SystemServiceRegistry
+ final Supplier<?> serviceSupplier = mNameToFactory.get(serviceName);
+ if (serviceSupplier != null) {
+ return serviceSupplier.get();
+ } else {
+ throw new UnsupportedOperationException(
+ "Service " + serviceName + " not yet supported under Ravenwood");
+ }
+ }
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ // TODO: pivot to using SystemServiceRegistry
+ final String serviceName = mClassToName.get(serviceClass);
+ if (serviceName != null) {
+ return serviceName;
+ } else {
+ throw new UnsupportedOperationException(
+ "Service " + serviceClass + " not yet supported under Ravenwood");
+ }
+ }
+
+ /**
+ * Wrap the given {@link Supplier} to become a memoized singleton.
+ */
+ private static <T> Supplier<T> asSingleton(Supplier<T> supplier) {
+ final Singleton<T> singleton = new Singleton<>() {
+ @Override
+ protected T create() {
+ return supplier.get();
+ }
+ };
+ return () -> {
+ return singleton.get();
+ };
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java
new file mode 100644
index 0000000..4244135
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+
+import android.content.AttributionSource;
+import android.os.PermissionEnforcer;
+
+public class RavenwoodPermissionEnforcer extends PermissionEnforcer {
+ @Override
+ protected int checkPermission(String permission, AttributionSource source) {
+ // For the moment, since Ravenwood doesn't offer cross-process capabilities, assume all
+ // permissions are granted during tests
+ return PERMISSION_GRANTED;
+ }
+
+ @Override
+ protected int checkPermission(String permission, int pid, int uid) {
+ // For the moment, since Ravenwood doesn't offer cross-process capabilities, assume all
+ // permissions are granted during tests
+ return PERMISSION_GRANTED;
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 1d5c79c..231cce9 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -24,11 +24,13 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.ServiceManager;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.os.RuntimeInit;
+import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Assert;
@@ -103,9 +105,10 @@
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
- ActivityManager.init$ravenwood(rule.mCurrentUser);
+ ServiceManager.init$ravenwood();
+ LocalServices.removeAllServicesForTest();
- com.android.server.LocalServices.removeAllServicesForTest();
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
if (rule.mProvideMainThread) {
final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
@@ -113,7 +116,12 @@
Looper.setMainLooperForTest(main.getLooper());
}
- InstrumentationRegistry.registerInstance(new Instrumentation(), Bundle.EMPTY);
+ rule.mContext = new RavenwoodContext();
+ rule.mInstrumentation = new Instrumentation();
+ rule.mInstrumentation.basicInit(rule.mContext);
+ InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
+
+ RavenwoodSystemServer.init(rule);
if (ENABLE_TIMEOUT_STACKS) {
sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
@@ -121,7 +129,7 @@
}
// Touch some references early to ensure they're <clinit>'ed
- Objects.requireNonNull(Build.IS_USERDEBUG);
+ Objects.requireNonNull(Build.TYPE);
Objects.requireNonNull(Build.VERSION.SDK);
}
@@ -130,17 +138,22 @@
sPendingTimeout.cancel(false);
}
+ RavenwoodSystemServer.reset(rule);
+
InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+ rule.mInstrumentation = null;
+ rule.mContext = null;
if (rule.mProvideMainThread) {
Looper.getMainLooper().quit();
Looper.clearMainLooperForTest();
}
- com.android.server.LocalServices.removeAllServicesForTest();
-
ActivityManager.reset$ravenwood();
+ LocalServices.removeAllServicesForTest();
+ ServiceManager.reset$ravenwood();
+
android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
new file mode 100644
index 0000000..bb280f4
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import android.hardware.SerialManager;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.utils.TimingsTraceAndSlog;
+
+public class RavenwoodSystemServer {
+ /**
+ * Set of services that we know how to provide under Ravenwood. We keep this set distinct
+ * from {@code com.android.server.SystemServer} to give us the ability to choose either
+ * "real" or "fake" implementations based on the commitments of the service owner.
+ *
+ * Map from {@code FooManager.class} to the {@code com.android.server.SystemService}
+ * lifecycle class name used to instantiate and drive that service.
+ */
+ private static final ArrayMap<Class<?>, String> sKnownServices = new ArrayMap<>();
+
+ // TODO: expand SystemService API to support dependency expression, so we don't need test
+ // authors to exhaustively declare all transitive services
+
+ static {
+ sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle");
+ }
+
+ private static TimingsTraceAndSlog sTimings;
+ private static SystemServiceManager sServiceManager;
+
+ public static void init(RavenwoodRule rule) {
+ // Avoid overhead if no services required
+ if (rule.mServicesRequired.isEmpty()) return;
+
+ sTimings = new TimingsTraceAndSlog();
+ sServiceManager = new SystemServiceManager(rule.mContext);
+ sServiceManager.setStartInfo(false,
+ SystemClock.elapsedRealtime(),
+ SystemClock.uptimeMillis());
+ LocalServices.addService(SystemServiceManager.class, sServiceManager);
+
+ for (Class<?> service : rule.mServicesRequired) {
+ final String target = sKnownServices.get(service);
+ if (target == null) {
+ throw new RuntimeException("The requested service " + service
+ + " is not yet supported under the Ravenwood deviceless testing "
+ + "environment; consider requesting support from the API owner or "
+ + "consider using Mockito; more details at go/ravenwood-docs");
+ } else {
+ sServiceManager.startService(target);
+ }
+ }
+ sServiceManager.sealStartedServices();
+
+ // TODO: expand to include additional boot phases when relevant
+ sServiceManager.startBootPhase(sTimings, SystemService.PHASE_SYSTEM_SERVICES_READY);
+ sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
+ }
+
+ public static void reset(RavenwoodRule rule) {
+ // TODO: consider introducing shutdown boot phases
+
+ LocalServices.removeServiceForTest(SystemServiceManager.class);
+ sServiceManager = null;
+ sTimings = null;
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index b90f112..a8c24fc 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -22,10 +22,13 @@
import static org.junit.Assert.fail;
+import android.app.Instrumentation;
+import android.content.Context;
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.util.ArraySet;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -122,6 +125,11 @@
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+ final ArraySet<Class<?>> mServicesRequired = new ArraySet<>();
+
+ volatile Context mContext;
+ volatile Instrumentation mInstrumentation;
+
public RavenwoodRule() {
}
@@ -192,6 +200,23 @@
return this;
}
+ /**
+ * Configure the set of system services that are required for this test to operate.
+ *
+ * For example, passing {@code android.hardware.SerialManager.class} as an argument will
+ * ensure that the underlying service is created, initialized, and ready to use for the
+ * duration of the test. The {@code SerialManager} instance can be obtained via
+ * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
+ * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ */
+ public Builder setServicesRequired(Class<?>... services) {
+ mRule.mServicesRequired.clear();
+ for (Class<?> service : services) {
+ mRule.mServicesRequired.add(service);
+ }
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
@@ -212,6 +237,28 @@
return IS_ON_RAVENWOOD;
}
+ /**
+ * Return a {@code Context} available for usage during the currently running test case.
+ *
+ * Each test should obtain needed information or references via this method;
+ * references must not be stored beyond the scope of a test case.
+ */
+ public Context getContext() {
+ return Objects.requireNonNull(mContext,
+ "Context is only available during @Test execution");
+ }
+
+ /**
+ * Return a {@code Instrumentation} available for usage during the currently running test case.
+ *
+ * Each test should obtain needed information or references via this method;
+ * references must not be stored beyond the scope of a test case.
+ */
+ public Instrumentation getInstrumentation() {
+ return Objects.requireNonNull(mInstrumentation,
+ "Instrumentation is only available during @Test execution");
+ }
+
static boolean shouldEnableOnDevice(Description description) {
if (description.isTest()) {
if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) {
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index b5baef6..4a4c290 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -99,6 +99,7 @@
android.util.StringBuilderPrinter
android.util.TeeWriter
android.util.TimeUtils
+android.util.TimingsTraceLog
android.util.UtilConfig
android.util.Xml
@@ -152,6 +153,7 @@
android.os.ParcelUuid
android.os.Parcelable
android.os.PatternMatcher
+android.os.PermissionEnforcer
android.os.PersistableBundle
android.os.PowerComponents
android.os.Process
@@ -159,6 +161,8 @@
android.os.RemoteCallbackList
android.os.RemoteException
android.os.ResultReceiver
+android.os.ServiceManager
+android.os.ServiceManager$ServiceNotFoundException
android.os.ServiceSpecificException
android.os.StrictMode
android.os.SystemClock
@@ -252,6 +256,9 @@
android.view.Display$Mode
android.view.DisplayInfo
+android.hardware.SerialManager
+android.hardware.SerialManagerInternal
+
android.telephony.ActivityStatsTechSpecificInfo
android.telephony.CellSignalStrength
android.telephony.ModemActivityInfo
@@ -310,3 +317,9 @@
com.google.android.collect.Lists
com.google.android.collect.Maps
com.google.android.collect.Sets
+
+com.android.server.SerialService
+com.android.server.SystemService
+com.android.server.SystemServiceManager
+
+com.android.server.utils.TimingsTraceAndSlog
diff --git a/ravenwood/ravenwood-services-jarjar-rules.txt b/ravenwood/ravenwood-services-jarjar-rules.txt
new file mode 100644
index 0000000..8fdd340
--- /dev/null
+++ b/ravenwood/ravenwood-services-jarjar-rules.txt
@@ -0,0 +1,11 @@
+# Ignore one-off class defined out in core/java/
+rule com.android.server.LocalServices @0
+rule com.android.server.pm.pkg.AndroidPackage @0
+rule com.android.server.pm.pkg.AndroidPackageSplit @0
+
+# Rename all other service internals so that tests can continue to statically
+# link services code when owners aren't ready to support on Ravenwood
+rule com.android.server.** repackaged.@0
+
+# TODO: support AIDL generated Parcelables via hoststubgen
+rule android.hardware.power.stats.** repackaged.@0
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
index 259aa70..a303626 100755
--- a/ravenwood/run-ravenwood-tests.sh
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -20,5 +20,9 @@
# "echo" is to remove the newlines
all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )"
-echo "Running tests: $all_tests"
-atest $all_tests
+run() {
+ echo "Running: $*"
+ "${@}"
+}
+
+run ${ATEST:-atest} $all_tests
diff --git a/ravenwood/services-test/Android.bp b/ravenwood/services-test/Android.bp
new file mode 100644
index 0000000..39858f0
--- /dev/null
+++ b/ravenwood/services-test/Android.bp
@@ -0,0 +1,21 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodServicesTest",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "test/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
new file mode 100644
index 0000000..c1dee5d
--- /dev/null
+++ b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ravenwood;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.SerialManager;
+import android.hardware.SerialManagerInternal;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodServicesTest {
+ private static final String TEST_VIRTUAL_PORT = "virtual:example";
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProcessSystem()
+ .setServicesRequired(SerialManager.class)
+ .build();
+
+ @Test
+ public void testDefined() {
+ final SerialManager fromName = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final SerialManager fromClass =
+ mRavenwood.getContext().getSystemService(SerialManager.class);
+ assertNotNull(fromName);
+ assertNotNull(fromClass);
+ assertEquals(fromName, fromClass);
+
+ assertNotNull(LocalServices.getService(SerialManagerInternal.class));
+ }
+
+ @Test
+ public void testSimple() {
+ // Verify that we can obtain a manager, and talk to the backend service, and that no
+ // serial ports are configured by default
+ final SerialManager service = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final String[] ports = service.getSerialPorts();
+ assertEquals(0, ports.length);
+ }
+
+ @Test
+ public void testDriven() {
+ final SerialManager service = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final SerialManagerInternal internal = LocalServices.getService(
+ SerialManagerInternal.class);
+
+ internal.addVirtualSerialPortForTest(TEST_VIRTUAL_PORT, () -> {
+ throw new UnsupportedOperationException(
+ "Needs socketpair() to offer accurate emulation");
+ });
+ final String[] ports = service.getSerialPorts();
+ assertEquals(1, ports.length);
+ assertEquals(TEST_VIRTUAL_PORT, ports[0]);
+ }
+}
diff --git a/ravenwood/services.core-ravenwood-policies.txt b/ravenwood/services.core-ravenwood-policies.txt
new file mode 100644
index 0000000..d8d563e
--- /dev/null
+++ b/ravenwood/services.core-ravenwood-policies.txt
@@ -0,0 +1,7 @@
+# Ravenwood "policy" file for services.core.
+
+# Keep all AIDL interfaces
+class :aidl stubclass
+
+# Keep all feature flag implementations
+class :feature_flags stubclass
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 351760b..a5bbc7e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -599,7 +599,7 @@
callback.onFullScreenMagnificationActivationState(
mDisplayId, mMagnificationActivated);
});
- mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+ mControllerCtx.getWindowManager().setFullscreenMagnificationActivated(
mDisplayId, activated);
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index eb661ce..6f45f60 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2417,7 +2417,7 @@
if (android.os.Flags.allowPrivateProfile()
&& android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
// Do not add widget providers for profiles with items restricted on home screen.
- if (mUserManager
+ if (info != null && mUserManager
.getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
return false;
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d47245e..5a34217 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2770,6 +2770,10 @@
+ id + " destroyed");
return;
}
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): id= " + authenticationId
+ + ", data=" + data);
+ }
final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId);
@@ -2823,12 +2827,18 @@
+ ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
}
if (result instanceof FillResponse) {
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): received FillResponse from"
+ + " authentication flow");
+ }
logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED);
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_SUCCESS);
replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
} else if (result instanceof GetCredentialResponse) {
- Slog.d(TAG, "Received GetCredentialResponse from authentication flow");
+ if (sDebug) {
+ Slog.d(TAG, "Received GetCredentialResponse from authentication flow");
+ }
boolean isCredmanCallbackInvoked = false;
if (Flags.autofillCredmanIntegration()) {
GetCredentialResponse response = (GetCredentialResponse) result;
@@ -2843,6 +2853,10 @@
}
}
} else if (result instanceof Dataset) {
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): received Dataset from"
+ + " authentication flow");
+ }
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
logAuthenticationStatusLocked(requestId,
MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 31766f2..ff07619 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -29,6 +29,7 @@
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -44,6 +45,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.modules.expresslog.Counter;
import java.util.Set;
@@ -104,6 +106,8 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
@NonNull
+ private final AttributionSource mAttributionSource;
+ @NonNull
private final ArraySet<UserHandle> mAllowedUsers;
@GuardedBy("mGenericWindowPolicyControllerLock")
private boolean mActivityLaunchAllowedByDefault;
@@ -144,6 +148,7 @@
*
* @param windowFlags The window flags that this controller is interested in.
* @param systemWindowFlags The system window flags that this controller is interested in.
+ * @param attributionSource The AttributionSource of the VirtualDevice owner application.
* @param allowedUsers The set of users that are allowed to stream in this display.
* @param activityLaunchAllowedByDefault Whether activities are default allowed to be launched
* or blocked.
@@ -169,6 +174,7 @@
public GenericWindowPolicyController(
int windowFlags,
int systemWindowFlags,
+ AttributionSource attributionSource,
@NonNull ArraySet<UserHandle> allowedUsers,
boolean activityLaunchAllowedByDefault,
@NonNull Set<ComponentName> activityPolicyExemptions,
@@ -184,6 +190,7 @@
boolean showTasksInHostDeviceRecents,
@Nullable ComponentName customHomeComponent) {
super();
+ mAttributionSource = attributionSource;
mAllowedUsers = allowedUsers;
mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
mActivityPolicyExemptions = activityPolicyExemptions;
@@ -436,6 +443,12 @@
if (!mIsMirrorDisplay && mActivityBlockedCallback != null) {
mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_activity_blocked_count",
+ mAttributionSource.getUid());
+ }
+
}
private static boolean isAllowedByPolicy(boolean allowedByDefault,
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 1b49f18e..9b72288 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringDef;
+import android.content.AttributionSource;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
@@ -38,6 +39,7 @@
import android.os.IInputConstants;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.view.Display;
import android.view.InputDevice;
@@ -45,6 +47,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
@@ -98,11 +101,12 @@
private final DisplayManagerInternal mDisplayManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final WindowManager mWindowManager;
+ private final AttributionSource mAttributionSource;
private final DeviceCreationThreadVerifier mThreadVerifier;
InputController(@NonNull Handler handler,
- @NonNull WindowManager windowManager) {
- this(new NativeWrapper(), handler, windowManager,
+ @NonNull WindowManager windowManager, AttributionSource attributionSource) {
+ this(new NativeWrapper(), handler, windowManager, attributionSource,
// Verify that virtual devices are not created on the handler thread.
() -> !handler.getLooper().isCurrentThread());
}
@@ -110,12 +114,14 @@
@VisibleForTesting
InputController(@NonNull NativeWrapper nativeWrapper,
@NonNull Handler handler, @NonNull WindowManager windowManager,
+ AttributionSource attributionSource,
@NonNull DeviceCreationThreadVerifier threadVerifier) {
mHandler = handler;
mNativeWrapper = nativeWrapper;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mWindowManager = windowManager;
+ mAttributionSource = attributionSource;
mThreadVerifier = threadVerifier;
}
@@ -838,6 +844,33 @@
new InputDeviceDescriptor(ptr, binderDeathRecipient, type, displayId, phys,
deviceName, inputDeviceId));
}
+
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ String metricId = getMetricIdForInputType(type);
+ if (metricId != null) {
+ Counter.logIncrementWithUid(metricId, mAttributionSource.getUid());
+ }
+ }
+ }
+
+ private static String getMetricIdForInputType(@InputDeviceDescriptor.Type int type) {
+ switch (type) {
+ case InputDeviceDescriptor.TYPE_KEYBOARD:
+ return "virtual_devices.value_virtual_keyboard_created_count";
+ case InputDeviceDescriptor.TYPE_MOUSE:
+ return "virtual_devices.value_virtual_mouse_created_count";
+ case InputDeviceDescriptor.TYPE_TOUCHSCREEN:
+ return "virtual_devices.value_virtual_touchscreen_created_count";
+ case InputDeviceDescriptor.TYPE_DPAD:
+ return "virtual_devices.value_virtual_dpad_created_count";
+ case InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD:
+ return "virtual_devices.value_virtual_navigationtouchpad_created_count";
+ case InputDeviceDescriptor.TYPE_STYLUS:
+ return "virtual_devices.value_virtual_stylus_created_count";
+ default:
+ Log.e(TAG, "No metric known for input type: " + type);
+ return null;
+ }
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index e9241dd..cf48180 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -23,6 +23,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.AttributionSource;
import android.hardware.SensorDirectChannel;
import android.os.Binder;
import android.os.IBinder;
@@ -35,6 +36,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.sensors.SensorManagerInternal;
@@ -71,14 +73,18 @@
private List<VirtualSensor> mVirtualSensorList = null;
@NonNull
+ private final AttributionSource mAttributionSource;
+ @NonNull
private final SensorManagerInternal.RuntimeSensorCallback mRuntimeSensorCallback;
private final SensorManagerInternal mSensorManagerInternal;
private final VirtualDeviceManagerInternal mVdmInternal;
public SensorController(@NonNull IVirtualDevice virtualDevice, int virtualDeviceId,
+ @NonNull AttributionSource attributionSource,
@Nullable IVirtualSensorCallback virtualSensorCallback,
@NonNull List<VirtualSensorConfig> sensors) {
mVirtualDeviceId = virtualDeviceId;
+ mAttributionSource = attributionSource;
mRuntimeSensorCallback = new RuntimeSensorCallbackWrapper(virtualSensorCallback);
mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
@@ -139,6 +145,11 @@
mSensorDescriptors.put(sensorToken, sensorDescriptor);
mVirtualSensors.put(handle, sensor);
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_sensors_created_count",
+ mAttributionSource.getUid());
+ }
}
boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f13f49a..6d731b2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -104,6 +104,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
@@ -152,6 +153,8 @@
private final int mOwnerUid;
private final VirtualDeviceLog mVirtualDeviceLog;
private final String mOwnerPackageName;
+ @NonNull
+ private final AttributionSource mAttributionSource;
private final int mDeviceId;
@Nullable
private final String mPersistentDeviceId;
@@ -288,6 +291,7 @@
super(PermissionEnforcer.fromContext(context));
mVirtualDeviceLog = virtualDeviceLog;
mOwnerPackageName = attributionSource.getPackageName();
+ mAttributionSource = attributionSource;
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
@@ -307,11 +311,11 @@
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
- context.getSystemService(WindowManager.class));
+ context.getSystemService(WindowManager.class), mAttributionSource);
} else {
mInputController = inputController;
}
- mSensorController = new SensorController(this, mDeviceId,
+ mSensorController = new SensorController(this, mDeviceId, mAttributionSource,
mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
mCameraAccessController = cameraAccessController;
if (mCameraAccessController != null) {
@@ -620,7 +624,7 @@
}
if (mVirtualAudioController == null) {
- mVirtualAudioController = new VirtualAudioController(mContext);
+ mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource);
GenericWindowPolicyController gwpc = mVirtualDisplays.get(
displayId).getWindowPolicyController();
mVirtualAudioController.startListening(gwpc, routingCallback,
@@ -1028,7 +1032,7 @@
if (mVirtualCameraController == null) {
throw new UnsupportedOperationException("Virtual camera controller is not available");
}
- mVirtualCameraController.registerCamera(cameraConfig);
+ mVirtualCameraController.registerCamera(cameraConfig, mAttributionSource);
}
@Override // Binder call
@@ -1110,6 +1114,7 @@
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ mAttributionSource,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
mActivityPolicyExemptions,
@@ -1179,6 +1184,11 @@
Binder.restoreCallingIdentity(token);
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_display_created_count",
+ mAttributionSource.getUid());
+ }
return displayId;
}
@@ -1220,6 +1230,12 @@
if ((display.getFlags() & FLAG_SECURE) == 0) {
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
+
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_secure_window_blocked_count",
+ mAttributionSource.getUid());
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 2168cb2..9ad73ca 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -482,6 +482,11 @@
}
});
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_devices_created_with_uid_count",
+ attributionSource.getUid());
+ }
return virtualDevice;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
index c91877a..4bffb76 100644
--- a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
+++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
@@ -23,6 +23,7 @@
import android.annotation.RequiresPermission;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.content.AttributionSource;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -35,6 +36,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.companion.virtual.GenericWindowPolicyController;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.AudioPlaybackDetector.AudioPlaybackCallback;
@@ -70,10 +72,16 @@
@GuardedBy("mCallbackLock")
private IAudioConfigChangedCallback mConfigChangedCallback;
- public VirtualAudioController(Context context) {
+ public VirtualAudioController(Context context, AttributionSource attributionSource) {
mContext = context;
mAudioPlaybackDetector = new AudioPlaybackDetector(context);
mAudioRecordingDetector = new AudioRecordingDetector(context);
+
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_audio_created_count",
+ attributionSource.getUid());
+ }
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 2d82b5e..3bb1e33 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -26,6 +26,7 @@
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.content.AttributionSource;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -35,6 +36,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import java.io.PrintWriter;
import java.util.Map;
@@ -76,7 +78,8 @@
*
* @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
*/
- public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
+ public void registerCamera(@NonNull VirtualCameraConfig cameraConfig,
+ AttributionSource attributionSource) {
checkConfigByPolicy(cameraConfig);
connectVirtualCameraServiceIfNeeded();
@@ -97,6 +100,11 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_camera_created_count",
+ attributionSource.getUid());
+ }
}
/**
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index ff903a0..82c2038 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -17,51 +17,123 @@
package com.android.server;
import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
import android.content.Context;
import android.hardware.ISerialManager;
+import android.hardware.SerialManagerInternal;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.io.File;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.function.Supplier;
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialService extends ISerialManager.Stub {
-
private final Context mContext;
- private final String[] mSerialPorts;
+
+ @GuardedBy("mSerialPorts")
+ private final LinkedHashMap<String, Supplier<ParcelFileDescriptor>> mSerialPorts =
+ new LinkedHashMap<>();
+
+ private static final String PREFIX_VIRTUAL = "virtual:";
public SerialService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
- mSerialPorts = context.getResources().getStringArray(
+
+ synchronized (mSerialPorts) {
+ final String[] serialPorts = getSerialPorts(context);
+ for (String serialPort : serialPorts) {
+ mSerialPorts.put(serialPort, () -> {
+ return native_open(serialPort);
+ });
+ }
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static String[] getSerialPorts(Context context) {
+ return context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
+ private static String[] getSerialPorts$ravenwood(Context context) {
+ return new String[0];
+ }
+
+ public static class Lifecycle extends SystemService {
+ private SerialService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new SerialService(getContext());
+ publishBinderService(Context.SERIAL_SERVICE, mService);
+ publishLocalService(SerialManagerInternal.class, mService.mInternal);
+ }
+ }
+
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public String[] getSerialPorts() {
super.getSerialPorts_enforcePermission();
- ArrayList<String> ports = new ArrayList<String>();
- for (int i = 0; i < mSerialPorts.length; i++) {
- String path = mSerialPorts[i];
- if (new File(path).exists()) {
- ports.add(path);
+ synchronized (mSerialPorts) {
+ final ArrayList<String> ports = new ArrayList<>();
+ for (String path : mSerialPorts.keySet()) {
+ if (path.startsWith(PREFIX_VIRTUAL) || new File(path).exists()) {
+ ports.add(path);
+ }
}
+ return ports.toArray(new String[ports.size()]);
}
- String[] result = new String[ports.size()];
- ports.toArray(result);
- return result;
}
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public ParcelFileDescriptor openSerialPort(String path) {
super.openSerialPort_enforcePermission();
- for (int i = 0; i < mSerialPorts.length; i++) {
- if (mSerialPorts[i].equals(path)) {
- return native_open(path);
+ synchronized (mSerialPorts) {
+ final Supplier<ParcelFileDescriptor> supplier = mSerialPorts.get(path);
+ if (supplier != null) {
+ return supplier.get();
+ } else {
+ throw new IllegalArgumentException("Invalid serial port " + path);
}
}
- throw new IllegalArgumentException("Invalid serial port " + path);
}
+ private final SerialManagerInternal mInternal = new SerialManagerInternal() {
+ @Override
+ public void addVirtualSerialPortForTest(@NonNull String name,
+ @NonNull Supplier<ParcelFileDescriptor> supplier) {
+ synchronized (mSerialPorts) {
+ Preconditions.checkState(!mSerialPorts.containsKey(name),
+ "Port " + name + " already defined");
+ Preconditions.checkArgument(name.startsWith(PREFIX_VIRTUAL),
+ "Port " + name + " must be under " + PREFIX_VIRTUAL);
+ mSerialPorts.put(name, supplier);
+ }
+ }
+
+ @Override
+ public void removeVirtualSerialPortForTest(@NonNull String name) {
+ synchronized (mSerialPorts) {
+ Preconditions.checkState(mSerialPorts.containsKey(name),
+ "Port " + name + " not yet defined");
+ Preconditions.checkArgument(name.startsWith(PREFIX_VIRTUAL),
+ "Port " + name + " must be under " + PREFIX_VIRTUAL);
+ mSerialPorts.remove(name);
+ }
+ }
+ };
+
private native ParcelFileDescriptor native_open(String path);
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 2e14abb..9189ea7 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -332,7 +332,6 @@
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
- private final ArraySet<String> mAutomaticRollbackDenylistedPackages = new ArraySet<>();
private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>();
// A map from package name of vendor APEXes that can be updated to an installer package name
// allowed to install updates for it.
@@ -499,10 +498,6 @@
return mRollbackWhitelistedPackages;
}
- public Set<String> getAutomaticRollbackDenylistedPackages() {
- return mAutomaticRollbackDenylistedPackages;
- }
-
public Set<String> getWhitelistedStagedInstallers() {
return mWhitelistedStagedInstallers;
}
@@ -1481,16 +1476,6 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
- case "automatic-rollback-denylisted-app": {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<" + name + "> without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mAutomaticRollbackDenylistedPackages.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } break;
case "whitelisted-staged-installer": {
if (allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 933d259..7dc9f10 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -66,6 +66,7 @@
* {@hide}
*/
@SystemApi(client = Client.SYSTEM_SERVER)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class SystemService {
/** @hide */
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index a05b84b..20816a1 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -67,6 +67,8 @@
*
* {@hide}
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@android.ravenwood.annotation.RavenwoodKeepStaticInitializer
public final class SystemServiceManager implements Dumpable {
private static final String TAG = SystemServiceManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -123,7 +125,8 @@
@GuardedBy("mTargetUsers")
@Nullable private TargetUser mCurrentUser;
- SystemServiceManager(Context context) {
+ @android.ravenwood.annotation.RavenwoodKeep
+ public SystemServiceManager(Context context) {
mContext = context;
mServices = new ArrayList<>();
mServiceClassnames = new ArraySet<>();
@@ -136,6 +139,7 @@
*
* @return The service instance.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public SystemService startService(String className) {
final Class<SystemService> serviceClass = loadClassFromLoader(className,
this.getClass().getClassLoader());
@@ -178,6 +182,7 @@
* Loads and initializes a class from the given classLoader. Returns the class.
*/
@SuppressWarnings("unchecked")
+ @android.ravenwood.annotation.RavenwoodKeep
private static Class<SystemService> loadClassFromLoader(String className,
ClassLoader classLoader) {
try {
@@ -201,6 +206,7 @@
* @return The service instance, never null.
* @throws RuntimeException if the service fails to start.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
@@ -237,6 +243,7 @@
}
}
+ @android.ravenwood.annotation.RavenwoodKeep
public void startService(@NonNull final SystemService service) {
// Check if already started
String className = service.getClass().getName();
@@ -261,7 +268,8 @@
}
/** Disallow starting new services after this call. */
- void sealStartedServices() {
+ @android.ravenwood.annotation.RavenwoodKeep
+ public void sealStartedServices() {
mServiceClassnames = Collections.emptySet();
mServices = Collections.unmodifiableList(mServices);
}
@@ -273,6 +281,7 @@
* @param t trace logger
* @param phase The boot phase to start.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public void startBootPhase(@NonNull TimingsTraceAndSlog t, int phase) {
if (phase <= mCurrentPhase) {
throw new IllegalArgumentException("Next phase must be larger than previous");
@@ -305,13 +314,23 @@
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
final long totalBootTime = SystemClock.uptimeMillis() - mRuntimeStartUptime;
t.logDuration("TotalBootTime", totalBootTime);
- SystemServerInitThreadPool.shutdown();
+ shutdownInitThreadPool();
}
}
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void shutdownInitThreadPool() {
+ SystemServerInitThreadPool.shutdown();
+ }
+
+ private void shutdownInitThreadPool$ravenwood() {
+ // Thread pool not configured yet on Ravenwood; ignored
+ }
+
/**
* @return true if system has completed the boot; false otherwise.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isBootCompleted() {
return mCurrentPhase >= SystemService.PHASE_BOOT_COMPLETED;
}
@@ -646,12 +665,14 @@
}
/** Logs the failure. That's all. Tests may rely on parsing it, so only modify carefully. */
+ @android.ravenwood.annotation.RavenwoodKeep
private void logFailure(String onWhat, TargetUser curUser, String serviceName, Exception ex) {
Slog.wtf(TAG, "SystemService failure: Failure reporting " + onWhat + " of user "
+ curUser + " to service " + serviceName, ex);
}
/** Sets the safe mode flag for services to query. */
+ @android.ravenwood.annotation.RavenwoodKeep
void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
}
@@ -661,6 +682,7 @@
*
* @return safe mode flag
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isSafeMode() {
return mSafeMode;
}
@@ -668,6 +690,7 @@
/**
* @return true if runtime was restarted, false if it's normal boot
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isRuntimeRestarted() {
return mRuntimeRestarted;
}
@@ -675,6 +698,7 @@
/**
* @return Time when SystemServer was started, in elapsed realtime.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public long getRuntimeStartElapsedTime() {
return mRuntimeStartElapsedTime;
}
@@ -682,17 +706,20 @@
/**
* @return Time when SystemServer was started, in uptime.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public long getRuntimeStartUptime() {
return mRuntimeStartUptime;
}
- void setStartInfo(boolean runtimeRestarted,
+ @android.ravenwood.annotation.RavenwoodKeep
+ public void setStartInfo(boolean runtimeRestarted,
long runtimeStartElapsedTime, long runtimeStartUptime) {
mRuntimeRestarted = runtimeRestarted;
mRuntimeStartElapsedTime = runtimeStartElapsedTime;
mRuntimeStartUptime = runtimeStartUptime;
}
+ @android.ravenwood.annotation.RavenwoodKeep
private void warnIfTooLong(long duration, SystemService service, String operation) {
if (duration > SERVICE_CALL_WARN_TIME_MS) {
Slog.w(TAG, "Service " + service.getClass().getName() + " took " + duration + " ms in "
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 05010f88..f1776f4 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,7 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
-import static android.app.Flags.enableNightModeCache;
+import static android.app.Flags.enableNightModeBinderCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -148,7 +148,7 @@
@Override
public void set(int mode) {
mNightModeValue = mode;
- if (enableNightModeCache()) {
+ if (enableNightModeBinderCache()) {
UiModeManager.invalidateNightModeCache();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bbd3ad0b..2dd2f8f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5478,6 +5478,7 @@
}
}
intents[i] = new Intent(intent);
+ intents[i].removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
}
if (resolvedTypes != null && resolvedTypes.length != intents.length) {
@@ -13666,9 +13667,13 @@
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
- // Refuse possible leaked file descriptors
- if (service != null && service.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (service != null) {
+ // Refuse possible leaked file descriptors
+ if (service.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
if (callingPackage == null) {
@@ -13897,9 +13902,13 @@
enforceNotIsolatedCaller("bindService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
- // Refuse possible leaked file descriptors
- if (service != null && service.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (service != null) {
+ // Refuse possible leaked file descriptors
+ if (service.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
if (callingPackage == null) {
@@ -15800,9 +15809,13 @@
}
final Intent verifyBroadcastLocked(Intent intent) {
- // Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (intent != null) {
+ // Refuse possible leaked file descriptors
+ if (intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
int flags = intent.getFlags();
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index df46e5d..9568116 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1628,6 +1628,7 @@
int appUid;
int logUid;
int processStateCurTop;
+ String mAdjType;
ProcessStateRecord mState;
void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
@@ -1642,6 +1643,7 @@
this.appUid = appUid;
this.logUid = logUid;
this.processStateCurTop = processStateCurTop;
+ mAdjType = app.mState.getAdjType();
this.mState = app.mState;
}
@@ -1650,14 +1652,14 @@
// App has a visible activity; only upgrade adjustment.
if (adj > VISIBLE_APP_ADJ) {
adj = VISIBLE_APP_ADJ;
- mState.setAdjType("vis-activity");
+ mAdjType = "vis-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
}
}
if (procState > processStateCurTop) {
procState = processStateCurTop;
- mState.setAdjType("vis-activity");
+ mAdjType = "vis-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to vis-activity (top): " + app);
@@ -1666,8 +1668,6 @@
if (schedGroup < SCHED_GROUP_DEFAULT) {
schedGroup = SCHED_GROUP_DEFAULT;
}
- mState.setCached(false);
- mState.setEmpty(false);
foregroundActivities = true;
mHasVisibleActivities = true;
}
@@ -1676,14 +1676,14 @@
public void onPausedActivity() {
if (adj > PERCEPTIBLE_APP_ADJ) {
adj = PERCEPTIBLE_APP_ADJ;
- mState.setAdjType("pause-activity");
+ mAdjType = "pause-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app);
}
}
if (procState > processStateCurTop) {
procState = processStateCurTop;
- mState.setAdjType("pause-activity");
+ mAdjType = "pause-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to pause-activity (top): " + app);
@@ -1692,8 +1692,6 @@
if (schedGroup < SCHED_GROUP_DEFAULT) {
schedGroup = SCHED_GROUP_DEFAULT;
}
- mState.setCached(false);
- mState.setEmpty(false);
foregroundActivities = true;
mHasVisibleActivities = false;
}
@@ -1702,7 +1700,7 @@
public void onStoppingActivity(boolean finishing) {
if (adj > PERCEPTIBLE_APP_ADJ) {
adj = PERCEPTIBLE_APP_ADJ;
- mState.setAdjType("stop-activity");
+ mAdjType = "stop-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to stop-activity: " + app);
@@ -1718,15 +1716,13 @@
if (!finishing) {
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
procState = PROCESS_STATE_LAST_ACTIVITY;
- mState.setAdjType("stop-activity");
+ mAdjType = "stop-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to stop-activity: " + app);
}
}
}
- mState.setCached(false);
- mState.setEmpty(false);
foregroundActivities = true;
mHasVisibleActivities = false;
}
@@ -1735,7 +1731,7 @@
public void onOtherActivity() {
if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
procState = PROCESS_STATE_CACHED_ACTIVITY;
- mState.setAdjType("cch-act");
+ mAdjType = "cch-act";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to cached activity: " + app);
@@ -1791,8 +1787,6 @@
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN);
state.setAdjSource(null);
state.setAdjTarget(null);
- state.setEmpty(false);
- state.setCached(false);
if (!couldRecurse || !cycleReEval) {
// Don't reset this flag when doing cycles re-evaluation.
state.setNoKillOnBgRestrictedAndIdle(false);
@@ -1944,8 +1938,6 @@
adj = cachedAdj;
procState = PROCESS_STATE_CACHED_EMPTY;
if (!couldRecurse || !state.containsCycle()) {
- state.setCached(true);
- state.setEmpty(true);
state.setAdjType("cch-empty");
}
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1964,6 +1956,7 @@
hasVisibleActivities = state.getCachedHasVisibleActivities();
procState = state.getCachedProcState();
schedGroup = state.getCachedSchedGroup();
+ state.setAdjType(state.getCachedAdjType());
}
if (procState > PROCESS_STATE_CACHED_RECENT && state.getCachedHasRecentTasks()) {
@@ -2021,7 +2014,6 @@
adj = newAdj;
procState = newProcState;
state.setAdjType(adjType);
- state.setCached(false);
schedGroup = SCHED_GROUP_DEFAULT;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -2079,7 +2071,6 @@
// thus out of background check), so we yes the best background level we can.
adj = PERCEPTIBLE_APP_ADJ;
procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
- state.setCached(false);
state.setAdjType("force-imp");
state.setAdjSource(state.getForcingToImportant());
schedGroup = SCHED_GROUP_DEFAULT;
@@ -2094,7 +2085,6 @@
// We don't want to kill the current heavy-weight process.
adj = HEAVY_WEIGHT_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("heavy");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
@@ -2115,7 +2105,6 @@
// home app, so we don't want to let it go into the background.
adj = HOME_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("home");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
@@ -2149,7 +2138,6 @@
if (adj > PREVIOUS_APP_ADJ) {
adj = PREVIOUS_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("previous");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
@@ -2197,7 +2185,6 @@
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
}
- state.setCached(false);
}
if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
procState = ActivityManager.PROCESS_STATE_BACKUP;
@@ -2251,7 +2238,6 @@
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to started service: " + app);
}
- state.setCached(false);
}
}
// If we have let the service slide into the background
@@ -2376,7 +2362,6 @@
adj = FOREGROUND_APP_ADJ;
state.setCurRawAdj(adj);
schedGroup = SCHED_GROUP_DEFAULT;
- state.setCached(false);
state.setAdjType("ext-provider");
state.setAdjTarget(cpr.name);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -2399,7 +2384,6 @@
if (adj > PREVIOUS_APP_ADJ) {
adj = PREVIOUS_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("recent-provider");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -2707,10 +2691,12 @@
if (adj > clientAdj) {
adjType = "cch-bound-ui-services";
}
- if (state.setCached(false, dryRun)) {
+
+ if (state.isCached() && dryRun) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
+
clientAdj = adj;
clientProcState = procState;
} else {
@@ -2795,12 +2781,14 @@
newAdj = adj;
}
}
+
if (!cstate.isCached()) {
- if (state.setCached(false, dryRun)) {
+ if (state.isCached() && dryRun) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
}
+
if (adj > newAdj) {
adj = newAdj;
if (state.setCurRawAdj(adj, dryRun)) {
@@ -2958,8 +2946,8 @@
schedGroup = SCHED_GROUP_DEFAULT;
}
}
+
if (!dryRun) {
- state.setCached(false);
state.setAdjType("service");
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
.REASON_SERVICE_IN_USE);
@@ -3000,7 +2988,6 @@
}
state.setCurCapability(capability);
- state.setEmpty(false);
return updated;
}
@@ -3090,7 +3077,8 @@
}
adjType = "provider";
}
- if (state.setCached(state.isCached() & cstate.isCached(), dryRun)) {
+
+ if (state.isCached() && !cstate.isCached() && dryRun) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
@@ -3157,7 +3145,6 @@
}
state.setCurCapability(capability);
- state.setEmpty(false);
return false;
}
@@ -3614,7 +3601,6 @@
state.setCurProcState(initialProcState);
state.setCurRawProcState(initialProcState);
state.setCurCapability(initialCapability);
- state.setCached(initialCached);
state.setCurAdj(ProcessList.FOREGROUND_APP_ADJ);
state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 9883f09..7009bd0 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1122,11 +1122,6 @@
mInFullBackup = inFullBackup;
}
- @GuardedBy("mService")
- public void setCached(boolean cached) {
- mState.setCached(cached);
- }
-
@Override
@GuardedBy("mService")
public boolean isCached() {
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 8362eaf..8de748e 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -24,6 +25,7 @@
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_STARTED_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
@@ -283,18 +285,6 @@
private long mLastTopTime = Long.MIN_VALUE;
/**
- * Is this an empty background process?
- */
- @GuardedBy("mService")
- private boolean mEmpty;
-
- /**
- * Is this a cached process?
- */
- @GuardedBy("mService")
- private boolean mCached;
-
- /**
* This is a system process, but not currently showing UI.
*/
@GuardedBy("mService")
@@ -395,7 +385,7 @@
private boolean mNoKillOnBgRestrictedAndIdle;
/**
- * Last set value of {@link #mCached}.
+ * Last set value of {@link #isCached()}.
*/
@GuardedBy("mService")
private boolean mSetCached;
@@ -408,7 +398,7 @@
/**
* The last time when the {@link #mNoKillOnBgRestrictedAndIdle} is false and the
- * {@link #mCached} is true, and either the former state is flipping from true to false
+ * {@link #isCached()} is true, and either the former state is flipping from true to false
* when latter state is true, or the latter state is flipping from false to true when the
* former state is false.
*/
@@ -446,6 +436,8 @@
};
@GuardedBy("mService")
+ private String mCachedAdjType = null;
+ @GuardedBy("mService")
private int mCachedAdj = ProcessList.INVALID_ADJ;
@GuardedBy("mService")
private boolean mCachedForegroundActivities = false;
@@ -535,7 +527,7 @@
@GuardedBy(anyOf = {"mService", "mProcLock"})
int getSetAdjWithServices() {
- if (mSetAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ if (mSetAdj >= CACHED_APP_MIN_ADJ) {
if (mHasStartedServices) {
return ProcessList.SERVICE_B_ADJ;
}
@@ -915,36 +907,13 @@
}
@GuardedBy("mService")
- void setEmpty(boolean empty) {
- mEmpty = empty;
- }
-
- @GuardedBy("mService")
boolean isEmpty() {
- return mEmpty;
- }
-
- @GuardedBy("mService")
- void setCached(boolean cached) {
- setCached(cached, false);
- }
-
- /**
- * @return {@code true} if it's a dry run and it's going to uncache the process
- * if it was a real run.
- */
- @GuardedBy("mService")
- boolean setCached(boolean cached, boolean dryRun) {
- if (dryRun) {
- return mCached && !cached;
- }
- mCached = cached;
- return false;
+ return mCurProcState >= PROCESS_STATE_CACHED_EMPTY;
}
@GuardedBy("mService")
boolean isCached() {
- return mCached;
+ return mCurAdj >= CACHED_APP_MIN_ADJ;
}
@GuardedBy("mService")
@@ -1041,6 +1010,7 @@
mCachedForegroundActivities = false;
mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ mCachedAdjType = null;
}
@GuardedBy("mService")
@@ -1152,6 +1122,7 @@
mCachedHasVisibleActivities = callback.mHasVisibleActivities ? VALUE_TRUE : VALUE_FALSE;
mCachedProcState = callback.procState;
mCachedSchedGroup = callback.schedGroup;
+ mCachedAdjType = callback.mAdjType;
if (mCachedAdj == ProcessList.VISIBLE_APP_ADJ) {
mCachedAdj += minLayer;
@@ -1179,6 +1150,11 @@
}
@GuardedBy("mService")
+ String getCachedAdjType() {
+ return mCachedAdjType;
+ }
+
+ @GuardedBy("mService")
boolean shouldScheduleLikeTopApp() {
return mScheduleLikeTopApp;
}
@@ -1381,8 +1357,8 @@
pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
pw.print(" pendingUiClean="); pw.println(mApp.mProfile.hasPendingUiClean());
}
- pw.print(prefix); pw.print("cached="); pw.print(mCached);
- pw.print(" empty="); pw.println(mEmpty);
+ pw.print(prefix); pw.print("cached="); pw.print(isCached());
+ pw.print(" empty="); pw.println(isEmpty());
if (mServiceB) {
pw.print(prefix); pw.print("serviceb="); pw.print(mServiceB);
pw.print(" serviceHighRam="); pw.println(mServiceHighRam);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ef93400..cb6d26f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5673,7 +5673,7 @@
final boolean isMuted = isStreamMutedByRingerOrZenMode(streamType);
final boolean muteAllowedBySco =
!(shouldRingSco && streamType == AudioSystem.STREAM_RING);
- final boolean shouldZenMute = shouldZenMuteStream(streamType);
+ final boolean shouldZenMute = isStreamAffectedByCurrentZen(streamType);
final boolean shouldMute = shouldZenMute || (ringerModeMute
&& isStreamAffectedByRingerMode(streamType) && muteAllowedBySco);
if (isMuted == shouldMute) continue;
@@ -6937,24 +6937,8 @@
return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
}
- private boolean shouldZenMuteStream(int streamType) {
- if (mNm.getZenMode() != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- return false;
- }
-
- NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy();
- final boolean muteAlarms = (zenPolicy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0;
- final boolean muteMedia = (zenPolicy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) == 0;
- final boolean muteSystem = (zenPolicy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0;
- final boolean muteNotificationAndRing = ZenModeConfig
- .areAllPriorityOnlyRingerSoundsMuted(zenPolicy);
- return muteAlarms && isAlarm(streamType)
- || muteMedia && isMedia(streamType)
- || muteSystem && isSystem(streamType)
- || muteNotificationAndRing && isNotificationOrRinger(streamType);
+ public boolean isStreamAffectedByCurrentZen(int streamType) {
+ return (mZenModeAffectedStreams & (1 << streamType)) != 0;
}
private boolean isStreamMutedByRingerOrZenMode(int streamType) {
@@ -6962,11 +6946,9 @@
}
/**
- * Notifications, ringer and system sounds are controlled by the ringer:
- * {@link ZenModeHelper.RingerModeDelegate#getRingerModeAffectedStreams(int)} but can
- * also be muted by DND based on the DND mode:
- * DND total silence: media and alarms streams can be muted by DND
- * DND alarms only: no streams additionally controlled by DND
+ * Volume streams can be muted based on the current DND state:
+ * DND total silence: ringer, notification, system, media and alarms streams muted by DND
+ * DND alarms only: ringer, notification, system streams muted by DND
* DND priority only: alarms, media, system, ringer and notification streams can be muted by
* DND. The current applied zenPolicy determines which streams will be muted by DND.
* @return true if changed, else false
@@ -6976,12 +6958,20 @@
return false;
}
+ // If DND is off, no streams are muted by DND
int zenModeAffectedStreams = 0;
final int zenMode = mNm.getZenMode();
if (zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_SYSTEM;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_NOTIFICATION;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_RING;
zenModeAffectedStreams |= 1 << AudioManager.STREAM_ALARM;
zenModeAffectedStreams |= 1 << AudioManager.STREAM_MUSIC;
+ } else if (zenMode == Settings.Global.ZEN_MODE_ALARMS) {
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_SYSTEM;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_NOTIFICATION;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_RING;
} else if (zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy();
if ((zenPolicy.priorityCategories
@@ -7023,7 +7013,6 @@
((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)|
(1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)),
UserHandle.USER_CURRENT);
-
if (mIsSingleVolume) {
ringerModeAffectedStreams = 0;
} else if (mRingerModeDelegate != null) {
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
deleted file mode 100644
index 9e60ba8..0000000
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsBiometricsTestCases"
- },
- {
- "name": "CtsBiometricsHostTestCases"
- }
- ]
-}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4c4cf608..d1374a5 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2161,7 +2161,7 @@
final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint();
final int size = points.size();
- if (size <= 0) {
+ if (size == 0) {
return null;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index fab769e..40e9198 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -75,6 +75,6 @@
protected enum Type {
THERMAL,
POWER,
- BEDTIME_MODE,
+ WEAR_BEDTIME_MODE,
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 2c02fc6..bc5fcb4 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -156,6 +156,8 @@
return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
} else if (mClamperType == Type.POWER) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
+ } else if (mClamperType == Type.WEAR_BEDTIME_MODE) {
+ return BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE;
} else {
Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
index 7e853bf..1902e35 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
@@ -64,7 +64,7 @@
@NonNull
@Override
Type getType() {
- return Type.BEDTIME_MODE;
+ return Type.WEAR_BEDTIME_MODE;
}
@Override
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index b963a4b..7e190dd 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -61,20 +61,26 @@
public abstract void setPulseGestureEnabled(boolean enabled);
/**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
+ * Atomically transfers an active touch gesture from one window to another, as identified by
+ * their input channels.
*
- * @param fromChannelToken The channel token of a window that currently has touch focus.
- * @param toChannelToken The channel token of the window that should receive touch focus in
- * place of the first.
- * @return {@code true} if the transfer was successful. {@code false} if the window with the
- * specified channel did not actually have touch focus at the time of the request.
+ * <p>Only the touch gesture that is currently being dispatched to a window associated with
+ * {@code fromChannelToken} will be effected. That window will no longer receive
+ * the touch gesture (i.e. it will receive {@link android.view.MotionEvent#ACTION_CANCEL}).
+ * A window associated with the {@code toChannelToken} will receive the rest of the gesture
+ * (i.e. beginning with {@link android.view.MotionEvent#ACTION_DOWN} or
+ * {@link android.view.MotionEvent#ACTION_POINTER_DOWN}).
+ *
+ * <p>Transferring touch gestures will have no impact on focused windows. If the {@code
+ * toChannelToken} window is focusable, this will not bring focus to that window.
+ *
+ * @param fromChannelToken The channel token of a window that has an active touch gesture.
+ * @param toChannelToken The channel token of the window that should receive the gesture in
+ * place of the first.
+ * @return True if the transfer was successful. False if the specified windows don't exist, or
+ * if the source window is not actively receiving a touch gesture at the time of the request.
*/
- public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+ public abstract boolean transferTouchGesture(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken);
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 574be34..7b18fb6 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -737,7 +737,9 @@
* @param destChannelToken The token of the window or input channel that should receive the
* gesture
* @return True if the transfer succeeded, false if there was no active touch gesture happening
+ * @deprecated Use {@link #transferTouchGesture(IBinder, IBinder)}
*/
+ @Deprecated
public boolean transferTouch(IBinder destChannelToken, int displayId) {
// TODO(b/162194035): Replace this with a SPY window
Objects.requireNonNull(destChannelToken, "destChannelToken must not be null");
@@ -1343,43 +1345,44 @@
}
/**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromChannel The channel of a window that currently has touch focus.
- * @param toChannel The channel of the window that should receive touch focus in
- * place of the first.
- * @param isDragDrop True if transfer touch focus for drag and drop.
- * @return True if the transfer was successful. False if the window with the
- * specified channel did not actually have touch focus at the time of the request.
+ * Start drag and drop.
+ *
+ * @param fromChannel The input channel that is currently receiving a touch gesture that should
+ * be turned into the drag pointer.
+ * @param dragAndDropChannel The input channel associated with the system drag window.
+ * @return true if drag and drop was successfully started, false otherwise.
*/
- public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
- @NonNull InputChannel toChannel, boolean isDragDrop) {
- return mNative.transferTouchFocus(fromChannel.getToken(), toChannel.getToken(),
- isDragDrop);
+ public boolean startDragAndDrop(@NonNull InputChannel fromChannel,
+ @NonNull InputChannel dragAndDropChannel) {
+ return mNative.transferTouchGesture(fromChannel.getToken(), dragAndDropChannel.getToken(),
+ true /* isDragDrop */);
}
/**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromChannelToken The channel token of a window that currently has touch focus.
- * @param toChannelToken The channel token of the window that should receive touch focus in
- * place of the first.
- * @return True if the transfer was successful. False if the window with the
- * specified channel did not actually have touch focus at the time of the request.
+ * Atomically transfers an active touch gesture from one window to another, as identified by
+ * their input channels.
+ *
+ * <p>Only the touch gesture that is currently being dispatched to a window associated with
+ * {@code fromChannelToken} will be effected. That window will no longer receive
+ * the touch gesture (i.e. it will receive {@link android.view.MotionEvent#ACTION_CANCEL}).
+ * A window associated with the {@code toChannelToken} will receive the rest of the gesture
+ * (i.e. beginning with {@link android.view.MotionEvent#ACTION_DOWN} or
+ * {@link android.view.MotionEvent#ACTION_POINTER_DOWN}).
+ *
+ * <p>Transferring touch gestures will have no impact on focused windows. If the {@code
+ * toChannelToken} window is focusable, this will not bring focus to that window.
+ *
+ * @param fromChannelToken The channel token of a window that has an active touch gesture.
+ * @param toChannelToken The channel token of the window that should receive the gesture in
+ * place of the first.
+ * @return True if the transfer was successful. False if the specified windows don't exist, or
+ * if the source window is not actively receiving a touch gesture at the time of the request.
*/
- public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+ public boolean transferTouchGesture(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken) {
Objects.requireNonNull(fromChannelToken);
Objects.requireNonNull(toChannelToken);
- return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
+ return mNative.transferTouchGesture(fromChannelToken, toChannelToken,
false /* isDragDrop */);
}
@@ -3312,9 +3315,9 @@
}
@Override
- public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+ public boolean transferTouchGesture(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken) {
- return InputManagerService.this.transferTouchFocus(fromChannelToken, toChannelToken);
+ return InputManagerService.this.transferTouchGesture(fromChannelToken, toChannelToken);
}
@Override
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index ebc784d..4b9f2cf 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -227,7 +227,12 @@
"LAUNCH_DEFAULT_FITNESS"),
LAUNCH_APPLICATION_BY_PACKAGE_NAME(
FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
- "LAUNCH_APPLICATION_BY_PACKAGE_NAME");
+ "LAUNCH_APPLICATION_BY_PACKAGE_NAME"),
+ DESKTOP_MODE(
+ FrameworkStatsLog
+ .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
+ "DESKTOP_MODE");
+
private final int mValue;
private final String mName;
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index b16df0f..972a9e3 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -110,13 +110,15 @@
void setMinTimeBetweenUserActivityPokes(long millis);
- boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ boolean transferTouchGesture(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
/**
* Transfer the current touch gesture to the window identified by 'destChannelToken' positioned
* on display with id 'displayId'.
+ * @deprecated Use {@link #transferTouchGesture(IBinder, IBinder, boolean)}
*/
+ @Deprecated
boolean transferTouch(IBinder destChannelToken, int displayId);
int getMousePointerSpeed();
@@ -359,10 +361,11 @@
public native void setMinTimeBetweenUserActivityPokes(long millis);
@Override
- public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ public native boolean transferTouchGesture(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
@Override
+ @Deprecated
public native boolean transferTouch(IBinder destChannelToken, int displayId);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index 977dbff..84a59b4 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -117,6 +117,30 @@
}
@AnyThread
+ void onStartInputResult(@NonNull InputBindResult res, int startInputSeq) {
+ if (mIsProxy) {
+ onStartInputResultInternal(res, startInputSeq);
+ } else {
+ mHandler.post(() -> onStartInputResultInternal(res, startInputSeq));
+ }
+ }
+
+ @AnyThread
+ private void onStartInputResultInternal(@NonNull InputBindResult res, int startInputSeq) {
+ try {
+ mTarget.onStartInputResult(res, startInputSeq);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ } finally {
+ // Dispose the channel if the input method is not local to this process
+ // because the remote proxy will get its own copy when unparceled.
+ if (res.channel != null && mIsProxy) {
+ res.channel.dispose();
+ }
+ }
+ }
+
+ @AnyThread
void onBindAccessibilityService(@NonNull InputBindResult res, int id) {
if (mIsProxy) {
onBindAccessibilityServiceInternal(res, id);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index bc169ca..b4d15e9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1539,7 +1539,13 @@
@Override
public void onStart() {
mService.publishLocalService();
- publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
+ IInputMethodManager.Stub service;
+ if (Flags.useZeroJankProxy()) {
+ service = new ZeroJankProxy(mService.mHandler::post, mService);
+ } else {
+ service = mService;
+ }
+ publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@@ -2216,6 +2222,14 @@
}
}
+ @Nullable
+ ClientState getClientState(IInputMethodClient client) {
+ synchronized (ImfLock.class) {
+ return mClientController.getClient(client.asBinder());
+ }
+ }
+
+ // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -3741,6 +3755,20 @@
return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS;
}
+ //TODO(b/293640003): merge with startInputOrWindowGainedFocus once Flags.useZeroJankProxy()
+ // is enabled.
+ @Override
+ public void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+ // implemented by ZeroJankProxy
+ }
+
@NonNull
@Override
public InputBindResult startInputOrWindowGainedFocus(
@@ -5808,7 +5836,7 @@
}
curHostInputToken = mCurHostInputToken;
}
- return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken);
+ return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
new file mode 100644
index 0000000..692fd7dc
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.server.inputmethod.InputMethodManagerService.TAG;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
+import com.android.internal.inputmethod.IImeTracker;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.view.IInputMethodManager;
+
+import java.io.FileDescriptor;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+/**
+ * A proxy that processes all {@link IInputMethodManager} calls asynchronously.
+ * @hide
+ */
+public class ZeroJankProxy extends IInputMethodManager.Stub {
+
+ private final IInputMethodManager mInner;
+ private final Executor mExecutor;
+
+ ZeroJankProxy(Executor executor, IInputMethodManager inner) {
+ mInner = inner;
+ mExecutor = executor;
+ }
+
+ private void offload(ThrowingRunnable r) {
+ offloadInner(r);
+ }
+
+ private void offload(Runnable r) {
+ offloadInner(r);
+ }
+
+ private void offloadInner(Runnable r) {
+ boolean useThrowingRunnable = r instanceof ThrowingRunnable;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ final long inner = Binder.clearCallingIdentity();
+ // Restoring calling identity, so we can still do permission checks on caller.
+ Binder.restoreCallingIdentity(identity);
+ try {
+ try {
+ if (useThrowingRunnable) {
+ ((ThrowingRunnable) r).runOrThrow();
+ } else {
+ r.run();
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in async call", e);
+ throw ExceptionUtils.propagate(e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(inner);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId) throws RemoteException {
+ offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
+ }
+
+ @Override
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException {
+ return mInner.getCurrentInputMethodInfoAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList(
+ int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException {
+ return mInner.getInputMethodList(userId, directBootAwareness);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException {
+ return mInner.getEnabledInputMethodList(userId);
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, int userId)
+ throws RemoteException {
+ return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
+ userId);
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException {
+ return mInner.getLastInputMethodSubtype(userId);
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ int lastClickTooType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason)
+ throws RemoteException {
+ offload(() -> mInner.showSoftInput(client, windowToken, statsToken, flags, lastClickTooType,
+ resultReceiver, reason));
+ return true;
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)
+ throws RemoteException {
+ offload(() -> mInner.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason));
+ return true;
+ }
+
+ @Override
+ public void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq)
+ throws RemoteException {
+ offload(() -> {
+ InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
+ windowToken, startInputFlags, softInputMode, windowFlags,
+ editorInfo,
+ inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion,
+ userId, imeDispatcher);
+ sendOnStartInputResult(client, result, startInputSeq);
+ });
+ }
+
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @Override
+ public InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher)
+ throws RemoteException {
+ // Should never be called when flag is enabled i.e. when this proxy is used.
+ return null;
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient client,
+ int auxiliarySubtypeMode)
+ throws RemoteException {
+ offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
+ }
+
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)
+ throws RemoteException {
+ mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public boolean isInputMethodPickerShownForTest() throws RemoteException {
+ super.isInputMethodPickerShownForTest_enforcePermission();
+ return mInner.isInputMethodPickerShownForTest();
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException {
+ return mInner.getCurrentInputMethodSubtype(userId);
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId) throws RemoteException {
+ mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId);
+ }
+
+ @Override
+ public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException {
+ mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client)
+ throws RemoteException {
+ return mInner.getInputMethodWindowVisibleHeight(client);
+ }
+
+ @Override
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible)
+ throws RemoteException {
+ // Already async TODO(b/293640003): ordering issues?
+ mInner.reportPerceptibleAsync(windowToken, perceptible);
+ }
+
+ @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @Override
+ public void removeImeSurface() throws RemoteException {
+ mInner.removeImeSurface();
+ }
+
+ @Override
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException {
+ mInner.removeImeSurfaceFromWindowAsync(windowToken);
+ }
+
+ @Override
+ public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException {
+ mInner.startProtoDump(bytes, i, s);
+ }
+
+ @Override
+ public boolean isImeTraceEnabled() throws RemoteException {
+ return mInner.isImeTraceEnabled();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void startImeTrace() throws RemoteException {
+ mInner.startImeTrace();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void stopImeTrace() throws RemoteException {
+ mInner.stopImeTrace();
+ }
+
+ @Override
+ public void startStylusHandwriting(IInputMethodClient client)
+ throws RemoteException {
+ offload(() -> mInner.startStylusHandwriting(client));
+ }
+
+ @Override
+ public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ offload(() -> mInner.startConnectionlessStylusHandwriting(
+ client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName,
+ callback));
+ }
+
+ @Override
+ public boolean acceptStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ try {
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ return mInner.acceptStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }, this::offload).get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void prepareStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ offload(() -> mInner.prepareStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName));
+ }
+
+ @Override
+ public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless)
+ throws RemoteException {
+ return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless);
+ }
+
+ @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @Override
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client)
+ throws RemoteException {
+ mInner.addVirtualStylusIdForTestSession(client);
+ }
+
+ @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @Override
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout)
+ throws RemoteException {
+ mInner.setStylusWindowIdleTimeoutForTest(client, timeout);
+ }
+
+ @Override
+ public IImeTracker getImeTrackerService() throws RemoteException {
+ return mInner.getImeTrackerService();
+ }
+
+ @BinderThread
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ ((InputMethodManagerService) mInner).onShellCommand(
+ in, out, err, args, callback, resultReceiver);
+ }
+
+ private void sendOnStartInputResult(
+ IInputMethodClient client, InputBindResult res, int startInputSeq) {
+ InputMethodManagerService service = (InputMethodManagerService) mInner;
+ final ClientState cs = service.getClientState(client);
+ if (cs != null && cs.mClient != null) {
+ cs.mClient.onStartInputResult(res, startInputSeq);
+ } else {
+ // client is unbound.
+ Slog.i(TAG, "Client that requested startInputOrWindowGainedFocus is no longer"
+ + " bound. InputBindResult: " + res + " for startInputSeq: " + startInputSeq);
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 3e8af27..e7e8096 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -312,14 +312,15 @@
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
- boolean allowRunningInForeground = record.isActive()
- && (playbackState == null || playbackState.isActive());
+ boolean isUserEngaged =
+ record.isActive() && (playbackState == null || playbackState.isActive());
Log.d(TAG, "onSessionActiveStateChanged: "
+ "record=" + record
+ "playbackState=" + playbackState
- + "allowRunningInForeground=" + allowRunningInForeground);
- setForegroundServiceAllowance(record, allowRunningInForeground);
+ + "allowRunningInForeground=" + isUserEngaged);
+ setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ reportMediaInteractionEvent(record, isUserEngaged);
mHandler.postSessionsChanged(record);
}
}
@@ -417,12 +418,14 @@
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
if (playbackState != null) {
- boolean allowRunningInForeground = playbackState.isActive() && record.isActive();
+ boolean isUserEngaged = playbackState.isActive() && record.isActive();
Log.d(TAG, "onSessionPlaybackStateChanged: "
+ "record=" + record
+ "playbackState=" + playbackState
- + "allowRunningInForeground=" + allowRunningInForeground);
- setForegroundServiceAllowance(record, allowRunningInForeground);
+ + "allowRunningInForeground=" + isUserEngaged);
+ setForegroundServiceAllowance(
+ record, /* allowRunningInForeground= */ isUserEngaged);
+ reportMediaInteractionEvent(record, isUserEngaged);
}
}
}
@@ -590,6 +593,7 @@
Log.d(TAG, "destroySessionLocked: record=" + session);
setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
+ reportMediaInteractionEvent(session, /* userEngaged= */ false);
mHandler.postSessionsChanged(session);
}
@@ -608,11 +612,9 @@
if (allowRunningInForeground) {
mActivityManagerInternal.startForegroundServiceDelegate(
foregroundServiceDelegationOptions, /* connection= */ null);
- reportMediaInteractionEvent(record, /* userEngaged= */ true);
} else {
mActivityManagerInternal.stopForegroundServiceDelegate(
foregroundServiceDelegationOptions);
- reportMediaInteractionEvent(record, /* userEngaged= */ false);
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 796d8d7..b5c51af 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -340,8 +340,6 @@
static final String TAG = NetworkPolicyLogger.TAG;
private static final boolean LOGD = NetworkPolicyLogger.LOGD;
private static final boolean LOGV = NetworkPolicyLogger.LOGV;
- // TODO: b/304347838 - Remove once the feature is in staging.
- private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
/**
* No opportunistic quota could be calculated from user data plan or data settings.
@@ -1063,8 +1061,7 @@
}
// The flag is boot-stable.
- mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK
- && Flags.networkBlockedForTopSleepingAndAbove();
+ mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
if (mBackgroundNetworkRestricted) {
// Firewall rules and UidBlockedState will get updated in
// updateRulesForGlobalChangeAL below.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 53ae60b..7455fe0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5720,6 +5720,14 @@
}
@Override
+ @Condition.State
+ public int getAutomaticZenRuleState(@NonNull String id) {
+ Objects.requireNonNull(id, "id is null");
+ enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRuleState");
+ return mZenModeHelper.getAutomaticZenRuleState(id);
+ }
+
+ @Override
public void setAutomaticZenRuleState(String id, Condition condition) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 54de197..efb8c84 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -856,6 +856,20 @@
}
}
+ @Condition.State
+ int getAutomaticZenRuleState(String id) {
+ synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return Condition.STATE_UNKNOWN;
+ }
+ ZenRule rule = mConfig.automaticRules.get(id);
+ if (rule == null || !canManageAutomaticZenRule(rule)) {
+ return Condition.STATE_UNKNOWN;
+ }
+ return rule.condition != null ? rule.condition.state : Condition.STATE_FALSE;
+ }
+ }
+
void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
int callingUid) {
requirePublicOrigin("setAutomaticZenRuleState", origin);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
new file mode 100644
index 0000000..a4c4347
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -0,0 +1,419 @@
+/*
+ * 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.ondeviceintelligence;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.AppGlobals;
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.DownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IRemoteStorageService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.SystemService;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This is the system service for handling calls on the {@link OnDeviceIntelligenceManager}. This
+ * service holds connection references to the underlying remote services i.e. the isolated service
+ * {@link android.service.ondeviceintelligence.OnDeviceTrustedInferenceService} and a regular
+ * service counter part {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService}.
+ *
+ * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
+ * the Inference service for each user, due to possible high memory footprint.
+ *
+ * @hide
+ */
+public class OnDeviceIntelligenceManagerService extends SystemService {
+
+ private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
+ private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
+
+ private final Context mContext;
+ protected final Object mLock = new Object();
+
+
+ private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+ private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
+ volatile boolean mIsServiceEnabled;
+
+ public OnDeviceIntelligenceManagerService(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+ /* allowIsolated = */true);
+ }
+
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_ON_DEVICE_INTELLIGENCE,
+ BackgroundThread.getExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ mIsServiceEnabled = isServiceEnabled();
+ }
+ }
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ if (keys.contains(KEY_SERVICE_ENABLED)) {
+ mIsServiceEnabled = isServiceEnabled();
+ }
+ }
+
+ private boolean isServiceEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_ON_DEVICE_INTELLIGENCE,
+ KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ }
+
+ private final class OnDeviceIntelligenceManagerInternal extends
+ IOnDeviceIntelligenceManager.Stub {
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getVersion(remoteCallback));
+ }
+
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getFeature(id, featureCallback));
+ }
+
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ listFeaturesCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.listFeatures(listFeaturesCallback));
+ }
+
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureDetailsCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getFeatureDetails(feature, featureDetailsCallback));
+ }
+
+ @Override
+ public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ downloadCallback.onDownloadFailed(
+ DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.requestFeatureDownload(feature, cancellationSignal,
+ downloadCallback));
+ }
+
+
+ @Override
+ public void requestTokenCount(Feature feature,
+ Content request, ICancellationSignal cancellationSignal,
+ ITokenCountCallback tokenCountcallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(tokenCountcallback);
+
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ tokenCountcallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteTrustedInferenceServiceInitialized();
+ mRemoteInferenceService.post(
+ service -> service.requestTokenCount(feature, request, cancellationSignal,
+ tokenCountcallback));
+ }
+
+ @Override
+ public void processRequest(Feature feature,
+ Content request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(responseCallback);
+ Objects.requireNonNull(request);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ responseCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteTrustedInferenceServiceInitialized();
+ mRemoteInferenceService.post(
+ service -> service.processRequest(feature, request, requestType,
+ cancellationSignal, processingSignal,
+ responseCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Content request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ streamingCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteTrustedInferenceServiceInitialized();
+ mRemoteInferenceService.post(
+ service -> service.processRequestStreaming(feature, request, requestType,
+ cancellationSignal, processingSignal,
+ streamingCallback));
+ }
+ }
+
+ private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException {
+ synchronized (mLock) {
+ if (mRemoteOnDeviceIntelligenceService == null) {
+ String serviceName = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceService);
+ validateService(serviceName, false);
+ mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
+ ComponentName.unflattenFromString(serviceName),
+ UserHandle.SYSTEM.getIdentifier());
+ }
+ }
+ }
+
+ private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+ synchronized (mLock) {
+ if (mRemoteInferenceService == null) {
+ String serviceName = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceTrustedInferenceService);
+ validateService(serviceName, true);
+ mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+ ComponentName.unflattenFromString(serviceName),
+ UserHandle.SYSTEM.getIdentifier());
+ mRemoteInferenceService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onConnected(
+ @NonNull IOnDeviceTrustedInferenceService service) {
+ try {
+ service.registerRemoteStorageService(
+ getIRemoteStorageService());
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to send connected event", ex);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @NonNull
+ private IRemoteStorageService.Stub getIRemoteStorageService() {
+ return new IRemoteStorageService.Stub() {
+ @Override
+ public void getReadOnlyFileDescriptor(
+ String filePath,
+ AndroidFuture<ParcelFileDescriptor> future) {
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getReadOnlyFileDescriptor(
+ filePath, future));
+ }
+
+ @Override
+ public void getReadOnlyFeatureFileDescriptorMap(
+ Feature feature,
+ RemoteCallback remoteCallback)
+ throws RemoteException {
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getReadOnlyFeatureFileDescriptorMap(
+ feature, remoteCallback));
+ }
+ };
+ }
+
+ @GuardedBy("mLock")
+ private void validateService(String serviceName, boolean checkIsolated)
+ throws RemoteException {
+ if (TextUtils.isEmpty(serviceName)) {
+ throw new RuntimeException("");
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(
+ serviceName);
+ ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
+ if (serviceInfo != null) {
+ if (!checkIsolated) {
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
+ return;
+ }
+
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+ if (!isIsolatedService(serviceInfo)) {
+ throw new SecurityException(
+ "Call required an isolated service, but the configured service: "
+ + serviceName + ", is not isolated");
+ }
+ } else {
+ throw new RuntimeException(
+ "Could not find service info for serviceName: " + serviceName);
+ }
+ }
+
+ private static void checkServiceRequiresPermission(ServiceInfo serviceInfo,
+ String requiredPermission) {
+ final String permission = serviceInfo.permission;
+ if (!requiredPermission.equals(permission)) {
+ throw new SecurityException(String.format(
+ "Service %s requires %s permission. Found %s permission",
+ serviceInfo.getComponentName(),
+ requiredPermission,
+ serviceInfo.permission));
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) {
+ return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
+ }
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
new file mode 100644
index 0000000..48258d7
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ondeviceintelligence;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+
+import com.android.internal.infra.ServiceConnector;
+
+/**
+ * Manages the connection to the remote on-device intelligence service. Also, handles unbinding
+ * logic set by the service implementation via a Secure Settings flag.
+ */
+public class RemoteOnDeviceIntelligenceService extends
+ ServiceConnector.Impl<IOnDeviceIntelligenceService> {
+ private static final String TAG =
+ RemoteOnDeviceIntelligenceService.class.getSimpleName();
+
+ RemoteOnDeviceIntelligenceService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ OnDeviceIntelligenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IOnDeviceIntelligenceService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Disable automatic unbinding.
+ // TODO: add logic to fetch this flag via SecureSettings.
+ return -1;
+ }
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
new file mode 100644
index 0000000..cc8e788
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+
+import com.android.internal.infra.ServiceConnector;
+
+
+/**
+ * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * logic set by the service implementation via a SecureSettings flag.
+ */
+public class RemoteOnDeviceTrustedInferenceService extends
+ ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+ /**
+ * Creates an instance of {@link ServiceConnector}
+ *
+ * See {@code protected} methods for optional parameters you can override.
+ *
+ * @param context to be used for {@link Context#bindServiceAsUser binding} and
+ * {@link Context#unbindService unbinding}
+ * @param userId to be used for {@link Context#bindServiceAsUser binding}
+ */
+ RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IOnDeviceTrustedInferenceService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Disable automatic unbinding.
+ // TODO: add logic to fetch this flag via SecureSettings.
+ return -1;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 524bad5..b6daed1 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -30,6 +30,7 @@
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -46,6 +47,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseSetArray;
@@ -63,8 +65,10 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
@@ -103,6 +107,24 @@
private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
mInstallerForegroundTimeFrames = new SparseArrayMap<>();
+ @VisibleForTesting
+ protected final PackageManagerInternal.PackageListObserver mPackageObserver =
+ new PackageManagerInternal.PackageListObserver() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
+ .sendToTarget();
+ }
+ };
+
public BackgroundInstallControlService(@NonNull Context context) {
this(new InjectorImpl(context));
}
@@ -258,6 +280,7 @@
String installerPackageName;
String initiatingPackageName;
+
try {
final InstallSourceInfo installInfo = mPackageManager.getInstallSourceInfo(packageName);
installerPackageName = installInfo.getInstallingPackageName();
@@ -280,7 +303,8 @@
// convert up-time to current time.
final long installTimestamp =
- System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+ System.currentTimeMillis() - (SystemClock.uptimeMillis()
+ - retrieveInstallStartTimestamp(packageName, userId, appInfo));
if (installedByAdb(initiatingPackageName)
|| wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -293,6 +317,35 @@
writeBackgroundInstalledPackagesToDisk();
}
+ private long retrieveInstallStartTimestamp(String packageName,
+ int userId, ApplicationInfo appInfo) {
+ long installStartTimestamp = appInfo.createTimestamp;
+
+ try {
+ Optional<PackageInstaller.SessionInfo> latestInstallSession =
+ getLatestInstallSession(packageName, userId);
+ if (latestInstallSession.isEmpty()) {
+ Slog.w(TAG, "Package's historical install session not found, falling back "
+ + "to appInfo.createTimestamp: " + packageName);
+ } else {
+ installStartTimestamp = latestInstallSession.get().getCreatedMillis();
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Retrieval of install time from historical session failed, falling "
+ + "back to appInfo.createTimestamp");
+ Slog.w(TAG, Log.getStackTraceString(e));
+ }
+ return installStartTimestamp;
+ }
+
+ private Optional<PackageInstaller.SessionInfo> getLatestInstallSession(
+ String packageName, int userId) {
+ List<PackageInstaller.SessionInfo> historicalSessions =
+ mPackageManagerInternal.getHistoricalSessions(userId).getList();
+ return historicalSessions.stream().filter(s -> packageName.equals(s.getAppPackageName()))
+ .max(Comparator.comparingLong(PackageInstaller.SessionInfo::getCreatedMillis));
+ }
+
// ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
// addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
@@ -496,22 +549,7 @@
publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
}
- mPackageManagerInternal.getPackageList(
- new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
- .sendToTarget();
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
- .sendToTarget();
- }
- });
+ mPackageManagerInternal.getPackageList(mPackageObserver);
}
// The foreground time frame (ForegroundTimeFrame) represents the period
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 76d87ff..d8d8dd2 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -350,18 +350,50 @@
public void registerPackageInstallerCallback(String callingPackage,
IPackageInstallerCallback callback) {
verifyCallingPackage(callingPackage);
- UserHandle callingIdUserHandle = new UserHandle(getCallingUserId());
- getPackageInstallerService().registerCallback(callback, eventUserId ->
- isEnabledProfileOf(callingIdUserHandle,
- new UserHandle(eventUserId), "shouldReceiveEvent"));
+ BroadcastCookie callerInfo =
+ new BroadcastCookie(
+ new UserHandle(getCallingUserId()),
+ callingPackage,
+ getCallingPid(),
+ getCallingUid());
+ getPackageInstallerService()
+ .registerCallback(
+ callback,
+ eventUserId ->
+ isEnabledProfileOf(
+ callerInfo,
+ new UserHandle(eventUserId),
+ "shouldReceiveEvent"));
+ }
+
+ @Override
+ public List<UserHandle> getUserProfiles() {
+ int[] userIds;
+ if (!canAccessHiddenProfile(getCallingUid(), getCallingPid())) {
+ userIds = mUm.getProfileIdsExcludingHidden(getCallingUserId(), /* enabled= */ true);
+ } else {
+ userIds = mUm.getEnabledProfileIds(getCallingUserId());
+ }
+ final List<UserHandle> result = new ArrayList<>(userIds.length);
+ for (int userId : userIds) {
+ result.add(UserHandle.of(userId));
+ }
+ return result;
}
@Override
public ParceledListSlice<SessionInfo> getAllSessions(String callingPackage) {
verifyCallingPackage(callingPackage);
List<SessionInfo> sessionInfos = new ArrayList<>();
- int[] userIds = mUm.getEnabledProfileIds(getCallingUserId());
final int callingUid = Binder.getCallingUid();
+
+ int[] userIds;
+ if (!canAccessHiddenProfile(callingUid, Binder.getCallingPid())) {
+ userIds = mUm.getProfileIdsExcludingHidden(getCallingUserId(), /* enabled= */ true);
+ } else {
+ userIds = mUm.getEnabledProfileIds(getCallingUserId());
+ }
+
final long token = Binder.clearCallingIdentity();
try {
for (int userId : userIds) {
@@ -389,7 +421,7 @@
mPackageInstallerService = ((PackageInstallerService) ((IPackageManager)
ServiceManager.getService("package")).getPackageInstaller());
} catch (RemoteException e) {
- Slog.wtf(TAG, "Error gettig IPackageInstaller", e);
+ Slog.wtf(TAG, "Error getting IPackageInstaller", e);
}
}
return mPackageInstallerService;
@@ -470,57 +502,86 @@
+ targetUserId + " from " + callingUserId + " not allowed");
return false;
}
-
- if (areHiddenApisChecksEnabled()
- && mUm.getUserProperties(UserHandle.of(targetUserId))
- .getProfileApiVisibility()
- == UserProperties.PROFILE_API_VISIBILITY_HIDDEN
- && !canAccessHiddenProfileInjected(callingUid, callingPid)) {
- return false;
- }
} finally {
injectRestoreCallingIdentity(ident);
}
+ if (isHiddenProfile(UserHandle.of(targetUserId))
+ && !canAccessHiddenProfile(callingUid, callingPid)) {
+ return false;
+ }
+
return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
message, true);
}
- boolean areHiddenApisChecksEnabled() {
- return android.os.Flags.allowPrivateProfile()
- && Flags.enableLauncherAppsHiddenProfileChecks()
- && Flags.enablePermissionToAccessHiddenProfiles();
+ private boolean isHiddenProfile(UserHandle targetUser) {
+ if (!Flags.enableLauncherAppsHiddenProfileChecks()) {
+ return false;
+ }
+
+ long identity = injectClearCallingIdentity();
+ try {
+ UserProperties properties = mUm.getUserProperties(targetUser);
+ if (properties == null) {
+ return false;
+ }
+
+ return properties.getProfileApiVisibility()
+ == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
+ } catch (IllegalArgumentException e) {
+ return false;
+ } finally {
+ injectRestoreCallingIdentity(identity);
+ }
}
private void verifyCallingPackage(String callingPackage) {
verifyCallingPackage(callingPackage, injectBinderCallingUid());
}
- boolean canAccessHiddenProfileInjected(int callingUid, int callingPid) {
- AndroidPackage callingPackage = mPackageManagerInternal.getPackage(callingUid);
- if (callingPackage == null) {
- return false;
- }
-
- if (!mRoleManager
- .getRoleHoldersAsUser(
- RoleManager.ROLE_HOME, UserHandle.getUserHandleForUid(callingUid))
- .contains(callingPackage.getPackageName())) {
- return false;
- }
-
- if (mContext.checkPermission(
- Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL, callingPid, callingUid)
- == PackageManager.PERMISSION_GRANTED) {
+ private boolean canAccessHiddenProfile(int callingUid, int callingPid) {
+ if (!areHiddenApisChecksEnabled()) {
return true;
}
- // TODO(b/321988638): add option to disable with a flag
- return mContext.checkPermission(
- android.Manifest.permission.ACCESS_HIDDEN_PROFILES,
- callingPid,
- callingUid)
- == PackageManager.PERMISSION_GRANTED;
+ long ident = injectClearCallingIdentity();
+ try {
+ AndroidPackage callingPackage = mPackageManagerInternal.getPackage(callingUid);
+ if (callingPackage == null) {
+ return false;
+ }
+
+ if (!mRoleManager
+ .getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME, UserHandle.getUserHandleForUid(callingUid))
+ .contains(callingPackage.getPackageName())) {
+ return false;
+ }
+ if (mContext.checkPermission(
+ Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL,
+ callingPid,
+ callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ // TODO(b/321988638): add option to disable with a flag
+ return mContext.checkPermission(
+ android.Manifest.permission.ACCESS_HIDDEN_PROFILES,
+ callingPid,
+ callingUid)
+ == PackageManager.PERMISSION_GRANTED;
+ } finally {
+ injectRestoreCallingIdentity(ident);
+ }
+ }
+
+ private boolean areHiddenApisChecksEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.enableHidingProfiles()
+ && Flags.enableLauncherAppsHiddenProfileChecks()
+ && Flags.enablePermissionToAccessHiddenProfiles();
}
@VisibleForTesting // We override it in unit tests
@@ -2068,12 +2129,18 @@
});
}
- /** Checks if user is a profile of or same as listeningUser.
- * and the user is enabled. */
- private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
- String debugMsg) {
- return mUserManagerInternal.isProfileAccessible(listeningUser.getIdentifier(),
- user.getIdentifier(), debugMsg, false);
+ /**
+ * Checks if user is a profile of or same as listeningUser and the target user is enabled
+ * and accessible for caller.
+ */
+ private boolean isEnabledProfileOf(
+ BroadcastCookie cookie, UserHandle user, String debugMsg) {
+ if (isHiddenProfile(user)
+ && !canAccessHiddenProfile(cookie.callingUid, cookie.callingPid)) {
+ return false;
+ }
+ return mUserManagerInternal.isProfileAccessible(
+ cookie.user.getIdentifier(), user.getIdentifier(), debugMsg, false);
}
/**
@@ -2305,7 +2372,7 @@
mListeners.getBroadcastItem(i);
final BroadcastCookie cookie =
(BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackageRemoved")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) {
continue;
}
if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId(
@@ -2344,7 +2411,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackageAdded")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
@@ -2378,7 +2445,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackageModified")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackageModified")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
@@ -2403,7 +2470,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesAvailable")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesAvailable")) {
continue;
}
final String[] filteredPackages =
@@ -2433,7 +2500,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesUnavailable")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesUnavailable")) {
continue;
}
final String[] filteredPackages =
@@ -2477,7 +2544,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesSuspended")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesSuspended")) {
continue;
}
final String[] filteredPackagesWithoutExtras =
@@ -2514,7 +2581,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesUnsuspended")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesUnsuspended")) {
continue;
}
final String[] filteredPackages =
@@ -2551,7 +2618,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onShortcutChanged")) {
+ if (!isEnabledProfileOf(cookie, user, "onShortcutChanged")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
@@ -2625,7 +2692,7 @@
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
+ if (!isEnabledProfileOf(cookie, mUser, "onLoadingProgressChanged")) {
continue;
}
if (!isPackageVisibleToListener(mPackageName, cookie, mUser)) {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 05e8f9a..df4e5a3 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -354,21 +354,18 @@
ps.setArchiveState(/* archiveState= */ null, userId);
}
}
- mPm.mBackgroundHandler.post(
- () -> {
- File iconsDir = getIconsDir(packageName, userId);
- if (!iconsDir.exists()) {
- return;
- }
- // TODO(b/319238030) Move this into installd.
- if (!FileUtils.deleteContentsAndDir(iconsDir)) {
- Slog.e(TAG, "Failed to clean up archive files for " + packageName);
- } else {
- if (DEBUG) {
- Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath());
- }
- }
- });
+ File iconsDir = getIconsDir(packageName, userId);
+ if (!iconsDir.exists()) {
+ return;
+ }
+ // TODO(b/319238030) Move this into installd.
+ if (!FileUtils.deleteContentsAndDir(iconsDir)) {
+ Slog.e(TAG, "Failed to clean up archive files for " + packageName);
+ } else {
+ if (DEBUG) {
+ Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath());
+ }
+ }
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 4f9ed03..0a3dfc0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -46,7 +46,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Disabled;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.Intent;
@@ -200,7 +200,7 @@
*/
@Overridable
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
@@ -1246,6 +1246,9 @@
ActivityManagerUtils.logUnsafeIntentEvent(
UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
filterCallingUid, intent, resolvedType, enforce);
+ if (android.security.Flags.enforceIntentFilterMatch()) {
+ intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+ }
if (enforce) {
Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
Slog.w(TAG, "Access blocked: " + comp.getComponentName());
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f222fe9..b505a7c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -973,7 +973,7 @@
mUsers = users != null ? users : new SparseArray<>();
mHandler = new MainHandler();
mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1,
- /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
+ /* keepAliveTime */ 24, TimeUnit.HOURS, new LinkedBlockingQueue<>());
mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
mUserDataPreparer = userDataPreparer;
mUserTypes = UserTypeFactory.getUserTypes();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc26018..b5cd943 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3509,6 +3509,16 @@
}
}
break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.enterDesktop(event.getDisplayId());
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
+ return true;
+ }
+ }
+ break;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 1050e8a..9ea143e 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -32,7 +32,6 @@
import java.util.ArrayList;
-@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = PowerCalculator.DEBUG;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 359678b..2a93255 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1212,13 +1212,20 @@
rollback.makeAvailable();
mPackageHealthObserver.notifyRollbackAvailable(rollback.info);
- // TODO(zezeozue): Provide API to explicitly start observing instead
- // of doing this for all rollbacks. If we do this for all rollbacks,
- // should document in PackageInstaller.SessionParams#setEnableRollback
- // After enabling and committing any rollback, observe packages and
- // prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ if (Flags.recoverabilityDetection()) {
+ if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and committing any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
+ mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
+ mRollbackLifetimeDurationInMillis);
+ }
+ } else {
+ mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
+ mRollbackLifetimeDurationInMillis);
+ }
runExpiration();
}
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
index 6e1e979..7b39775 100644
--- a/services/core/java/com/android/server/search/Searchables.java
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -147,6 +147,9 @@
Log.e(LOG_TAG, "Error getting activity info " + re);
return null;
}
+ if (ai == null) {
+ return null;
+ }
String refActivityName = null;
// First look for activity-specific reference
diff --git a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
index b45c962..d8dfd9f 100644
--- a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
+++ b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
@@ -23,6 +23,7 @@
/**
* Helper class for reporting boot and shutdown timing metrics, also logging to {@link Slog}.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class TimingsTraceAndSlog extends TimingsTraceLog {
/**
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index fab0430..99ce3e2 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -31,6 +31,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
import android.app.UidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -145,8 +146,6 @@
PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT));
- private static final IntentFilter USER_SWITCHED_INTENT_FILTER =
- new IntentFilter(Intent.ACTION_USER_SWITCHED);
private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER =
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
@@ -162,9 +161,11 @@
@VisibleForTesting
final SettingsContentObserver mSettingObserver;
@VisibleForTesting
- final MyUidObserver mUidObserver;
- @VisibleForTesting
final SettingsBroadcastReceiver mSettingChangeReceiver;
+ @VisibleForTesting
+ final VibrationUidObserver mUidObserver;
+ @VisibleForTesting
+ final VibrationUserSwitchObserver mUserSwitchObserver;
@GuardedBy("mLock")
private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
@@ -205,8 +206,9 @@
mContext = context;
mVibrationConfig = config;
mSettingObserver = new SettingsContentObserver(handler);
- mUidObserver = new MyUidObserver();
mSettingChangeReceiver = new SettingsBroadcastReceiver();
+ mUidObserver = new VibrationUidObserver();
+ mUserSwitchObserver = new VibrationUserSwitchObserver();
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
.getSystemUiServiceComponent().getPackageName();
@@ -245,7 +247,13 @@
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
- ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ ActivityManager.PROCESS_STATE_UNKNOWN, /* callingPackage= */ null);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
@@ -270,7 +278,6 @@
}
});
- registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
// Listen to all settings that might affect the result of Vibrator.getVibrationIntensity.
@@ -540,41 +547,44 @@
/** Update all cached settings and triggers registered listeners. */
void update() {
- updateSettings();
+ updateSettings(UserHandle.USER_CURRENT);
updateRingerMode();
notifyListeners();
}
- private void updateSettings() {
+ private void updateSettings(int userHandle) {
synchronized (mLock) {
- mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
- mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
+ mVibrateInputDevices =
+ loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0, userHandle) > 0;
+ mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1, userHandle) > 0;
mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED,
- mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0;
+ mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0, userHandle) > 0;
int alarmIntensity = toIntensity(
- loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_ALARM));
int defaultHapticFeedbackIntensity = getDefaultIntensity(USAGE_TOUCH);
int hapticFeedbackIntensity = toIntensity(
- loadSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, -1),
+ loadSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, -1, userHandle),
defaultHapticFeedbackIntensity);
int positiveHapticFeedbackIntensity = toPositiveIntensity(
hapticFeedbackIntensity, defaultHapticFeedbackIntensity);
int hardwareFeedbackIntensity = toIntensity(
- loadSystemSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, -1),
+ loadSystemSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, -1,
+ userHandle),
positiveHapticFeedbackIntensity);
int mediaIntensity = toIntensity(
- loadSystemSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_MEDIA));
int defaultNotificationIntensity = getDefaultIntensity(USAGE_NOTIFICATION);
int notificationIntensity = toIntensity(
- loadSystemSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, -1,
+ userHandle),
defaultNotificationIntensity);
int positiveNotificationIntensity = toPositiveIntensity(
notificationIntensity, defaultNotificationIntensity);
int ringIntensity = toIntensity(
- loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_RINGTONE));
mCurrentVibrationIntensities.clear();
@@ -593,7 +603,7 @@
mCurrentVibrationIntensities.put(USAGE_HARDWARE_FEEDBACK, hardwareFeedbackIntensity);
mCurrentVibrationIntensities.put(USAGE_PHYSICAL_EMULATION, hardwareFeedbackIntensity);
- if (!loadBooleanSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED)) {
+ if (!loadBooleanSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, userHandle)) {
// Make sure deprecated boolean setting still disables touch vibrations.
mCurrentVibrationIntensities.put(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_OFF);
} else {
@@ -744,14 +754,13 @@
return value;
}
- private boolean loadBooleanSetting(String settingKey) {
- return Settings.System.getIntForUser(mContext.getContentResolver(),
- settingKey, 0, UserHandle.USER_CURRENT) != 0;
+ private boolean loadBooleanSetting(String settingKey, int userHandle) {
+ return loadSystemSetting(settingKey, 0, userHandle) != 0;
}
- private int loadSystemSetting(String settingName, int defaultValue) {
+ private int loadSystemSetting(String settingName, int defaultValue, int userHandle) {
return Settings.System.getIntForUser(mContext.getContentResolver(),
- settingName, defaultValue, UserHandle.USER_CURRENT);
+ settingName, defaultValue, userHandle);
}
private void registerSettingsObserver(Uri settingUri) {
@@ -828,24 +837,18 @@
@Override
public void onChange(boolean selfChange) {
- updateSettings();
+ updateSettings(UserHandle.USER_CURRENT);
notifyListeners();
}
}
- /**
- * Implementation of {@link BroadcastReceiver} to update settings on current user or ringer
- * mode change.
- */
+ /** Implementation of {@link BroadcastReceiver} to update on ringer mode change. */
@VisibleForTesting
final class SettingsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- // Reload all settings, as they are user-based.
- update();
- } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
+ if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
updateRingerMode();
notifyListeners();
}
@@ -854,7 +857,7 @@
/** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
@VisibleForTesting
- final class MyUidObserver extends UidObserver {
+ final class VibrationUidObserver extends UidObserver {
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
@@ -878,4 +881,23 @@
}
}
}
+
+ /** Implementation of {@link SynchronousUserSwitchObserver} to update on user switch. */
+ @VisibleForTesting
+ final class VibrationUserSwitchObserver extends SynchronousUserSwitchObserver {
+
+ @Override
+ public void onUserSwitching(int newUserId) {
+ // Reload settings early based on new user id.
+ updateSettings(newUserId);
+ notifyListeners();
+ }
+
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ // Reload all settings including ones from AudioManager,
+ // as they are based on UserHandle.USER_CURRENT.
+ update();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 759450b..5d172a9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -2220,9 +2220,12 @@
// only cancel background vibrations.
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
- HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(),
- Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, combined, attrs,
- commonOptions.description, deathBinder);
+ int uid = Binder.getCallingUid();
+ // Resolve the package name for the client based on the process UID, to cover cases like
+ // rooted shell clients using ROOT_UID.
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, SHELL_PACKAGE_NAME);
+ HalVibration vib = vibrateWithPermissionCheck(uid, Context.DEVICE_ID_DEFAULT,
+ resolvedPackageName, combined, attrs, commonOptions.description, deathBinder);
maybeWaitOnVibration(vib, commonOptions);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 118985a..764170a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -52,6 +52,7 @@
import android.app.ILocalWallpaperColorConsumer;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
+import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.UidObserver;
import android.app.UserSwitchObserver;
@@ -340,8 +341,7 @@
// If this was the system wallpaper, rebind...
wallpaper.mBindSource = BindSource.SET_STATIC;
- bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
- callback);
+ bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, callback);
}
if (lockWallpaperChanged) {
@@ -368,11 +368,7 @@
if (DEBUG) {
Slog.v(TAG, "Lock screen wallpaper changed to same as home");
}
- final WallpaperData lockedWallpaper = mLockWallpaperMap.get(
- mWallpaper.userId);
- if (lockedWallpaper != null) {
- detachWallpaperLocked(lockedWallpaper);
- }
+ detachWallpaperLocked(mLockWallpaperMap.get(mWallpaper.userId));
clearWallpaperBitmaps(mWallpaper.userId, FLAG_LOCK);
mLockWallpaperMap.remove(wallpaper.userId);
}
@@ -1696,7 +1692,7 @@
sWallpaperType.forEach((type, filename) -> {
final File record = new File(getWallpaperDir(userID), filename);
if (record.exists()) {
- Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type
+ Slog.w(TAG, "User:" + userID + ", wallpaper type = " + type
+ ", wallpaper fail detect!! reset to default wallpaper");
clearWallpaperBitmaps(userID, type);
record.delete();
@@ -1792,10 +1788,23 @@
systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
systemWallpaper.wallpaperObserver.startWatching();
}
- if (lockWallpaper != systemWallpaper) {
- switchWallpaper(lockWallpaper, null);
+ if (Flags.reorderWallpaperDuringUserSwitch()) {
+ detachWallpaperLocked(mLastLockWallpaper);
+ detachWallpaperLocked(mLastWallpaper);
+ if (lockWallpaper == systemWallpaper) {
+ switchWallpaper(systemWallpaper, reply);
+ } else {
+ KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+ boolean isDeviceSecure = km != null && km.isDeviceSecure(userId);
+ switchWallpaper(isDeviceSecure ? lockWallpaper : systemWallpaper, reply);
+ switchWallpaper(isDeviceSecure ? systemWallpaper : lockWallpaper, null);
+ }
+ } else {
+ if (lockWallpaper != systemWallpaper) {
+ switchWallpaper(lockWallpaper, null);
+ }
+ switchWallpaper(systemWallpaper, reply);
}
- switchWallpaper(systemWallpaper, reply);
mInitialUserSwitch = false;
}
@@ -3385,10 +3394,10 @@
boolean homeUpdated = (newWallpaper.mWhich & FLAG_SYSTEM) != 0;
boolean lockUpdated = (newWallpaper.mWhich & FLAG_LOCK) != 0;
boolean systemWillBecomeLock = newWallpaper.mSystemWasBoth && !lockUpdated;
- if (mLastWallpaper != null && homeUpdated && !systemWillBecomeLock) {
+ if (homeUpdated && !systemWillBecomeLock) {
detachWallpaperLocked(mLastWallpaper);
}
- if (mLastLockWallpaper != null && lockUpdated) {
+ if (lockUpdated) {
detachWallpaperLocked(mLastLockWallpaper);
}
}
@@ -3396,7 +3405,7 @@
// Frees up all rendering resources used by the given wallpaper so that the WallpaperData object
// can be reused: detaches Engine, unbinds WallpaperService, etc.
private void detachWallpaperLocked(WallpaperData wallpaper) {
- if (wallpaper.connection != null) {
+ if (wallpaper != null && wallpaper.connection != null) {
if (DEBUG) {
Slog.v(TAG, "Detaching wallpaper: " + wallpaper);
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 00c3026..d05482d 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -55,6 +55,7 @@
import com.android.server.utils.quota.MultiRateLimiter;
import java.io.FileDescriptor;
+import java.time.Duration;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -106,7 +107,7 @@
private final Context mContext;
private final AtomicInteger mNextDataRequestObserverId = new AtomicInteger(1);
private final Set<DataRequestObserverContext> mDataRequestObserverContexts = new HashSet<>();
- private final MultiRateLimiter mDataRequestRateLimiter;
+ @NonNull private volatile MultiRateLimiter mDataRequestRateLimiter;
volatile boolean mIsServiceEnabled;
public WearableSensingManagerService(Context context) {
@@ -238,6 +239,57 @@
}
}
+ /**
+ * Sets the window size used in data request rate limiting.
+ *
+ * <p>The new value will not be reflected in {@link
+ * WearableSensingDataRequest#getRateLimitWindowSize()}.
+ *
+ * <p>{@code windowSize} will be automatically capped between
+ * com.android.server.utils.quota.QuotaTracker#MIN_WINDOW_SIZE_MS and
+ * com.android.server.utils.quota.QuotaTracker#MAX_WINDOW_SIZE_MS
+ *
+ * <p>The current rate limit will also be reset.
+ *
+ * <p>This method is only used for testing and must not be called in production code because
+ * it effectively bypasses the rate limiting introduced to enhance privacy protection.
+ */
+ @VisibleForTesting
+ void setDataRequestRateLimitWindowSize(@NonNull Duration windowSize) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Setting the data request rate limit window size to %s. This also resets"
+ + " the current limit and should only be callable from a test.",
+ windowSize));
+ mDataRequestRateLimiter =
+ new MultiRateLimiter.Builder(mContext)
+ .addRateLimit(WearableSensingDataRequest.getRateLimit(), windowSize)
+ .build();
+ }
+
+ /**
+ * Resets the window size used in data request rate limiting back to the default value.
+ *
+ * <p>The current rate limit will also be reset.
+ *
+ * <p>This method is only used for testing and must not be called in production code because
+ * it effectively bypasses the rate limiting introduced to enhance privacy protection.
+ */
+ @VisibleForTesting
+ void resetDataRequestRateLimitWindowSize() {
+ Slog.w(
+ TAG,
+ "Resetting the data request rate limit window size back to the default value. This"
+ + " also resets the current limit and should only be callable from a test.");
+ mDataRequestRateLimiter =
+ new MultiRateLimiter.Builder(mContext)
+ .addRateLimit(
+ WearableSensingDataRequest.getRateLimit(),
+ WearableSensingDataRequest.getRateLimitWindowSize())
+ .build();
+ }
+
private DataRequestObserverContext getDataRequestObserverContext(
int dataType, int userId, PendingIntent dataRequestPendingIntent) {
synchronized (mDataRequestObserverContexts) {
diff --git a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
index 842bccb..0a9cf34 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
@@ -29,6 +29,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.time.Duration;
final class WearableSensingShellCommand extends ShellCommand {
private static final String TAG = WearableSensingShellCommand.class.getSimpleName();
@@ -90,6 +91,8 @@
return getBoundPackageName();
case "set-temporary-service":
return setTemporaryService();
+ case "set-data-request-rate-limit-window-size":
+ return setDataRequestRateLimitWindowSize();
default:
return handleDefaultCommands(cmd);
}
@@ -114,6 +117,11 @@
pw.println(" set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]");
pw.println(" Temporarily (for DURATION ms) changes the service implementation.");
pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println(" set-data-request-rate-limit-window-size WINDOW_SIZE");
+ pw.println(" Set the window size used in data request rate limiting to WINDOW_SIZE"
+ + " seconds.");
+ pw.println(" positive WINDOW_SIZE smaller than 20 will be automatically set to 20.");
+ pw.println(" To reset, call with 0 or a negative WINDOW_SIZE.");
}
private int createDataStream() {
@@ -209,4 +217,20 @@
resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
return 0;
}
+
+ private int setDataRequestRateLimitWindowSize() {
+ Slog.d(TAG, "setDataRequestRateLimitWindowSize");
+ int windowSizeSeconds = Integer.parseInt(getNextArgRequired());
+ if (windowSizeSeconds <= 0) {
+ mService.resetDataRequestRateLimitWindowSize();
+ } else {
+ // 20 is the minimum window size supported by the rate limiter.
+ // It is defined by com.android.server.utils.quota.QuotaTracker#MIN_WINDOW_SIZE_MS
+ if (windowSizeSeconds < 20) {
+ windowSizeSeconds = 20;
+ }
+ mService.setDataRequestRateLimitWindowSize(Duration.ofSeconds(windowSizeSeconds));
+ }
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index e5c743c..fd4b061 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -147,6 +147,7 @@
@Nullable
protected abstract ActivityRecord findAppTokenForSnapshot(TYPE source);
protected abstract boolean use16BitFormat();
+ protected abstract Rect getLetterboxInsets(ActivityRecord topActivity);
/**
* This is different than {@link #recordSnapshotInner(TYPE)} because it doesn't store
@@ -309,7 +310,7 @@
final WindowState mainWindow = result.second;
final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
mainWindow.getInsetsStateWithVisibilityOverride());
- final Rect letterboxInsets = activity.getLetterboxInsets();
+ final Rect letterboxInsets = getLetterboxInsets(activity);
InsetUtils.addInsets(contentInsets, letterboxInsets);
builder.setIsRealSnapshot(true);
builder.setId(System.currentTimeMillis());
@@ -335,22 +336,27 @@
final Configuration taskConfig = activity.getTask().getConfiguration();
final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
final Rect outCrop = new Rect();
+ final Point taskSize = new Point();
final Transition.ChangeInfo changeInfo = mCurrentChangeInfo;
if (changeInfo != null && changeInfo.mRotation != displayRotation) {
// For example, the source is closing and display rotation changes at the same time.
// The snapshot should record the state in previous rotation.
outCrop.set(changeInfo.mAbsoluteBounds);
+ taskSize.set(changeInfo.mAbsoluteBounds.right, changeInfo.mAbsoluteBounds.bottom);
builder.setRotation(changeInfo.mRotation);
builder.setOrientation(changeInfo.mAbsoluteBounds.height()
>= changeInfo.mAbsoluteBounds.width()
? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE);
} else {
- outCrop.set(taskConfig.windowConfiguration.getBounds());
+ final Configuration srcConfig = source.getConfiguration();
+ outCrop.set(srcConfig.windowConfiguration.getBounds());
+ final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ taskSize.set(taskBounds.width(), taskBounds.height());
builder.setRotation(displayRotation);
- builder.setOrientation(taskConfig.orientation);
+ builder.setOrientation(srcConfig.orientation);
}
outCrop.offsetTo(0, 0);
- builder.setTaskSize(new Point(outCrop.right, outCrop.bottom));
+ builder.setTaskSize(taskSize);
return outCrop;
}
@@ -438,7 +444,7 @@
return null;
}
final Rect contentInsets = new Rect(systemBarInsets);
- final Rect letterboxInsets = topActivity.getLetterboxInsets();
+ final Rect letterboxInsets = getLetterboxInsets(topActivity);
InsetUtils.addInsets(contentInsets, letterboxInsets);
// Note, the app theme snapshot is never translucent because we enforce a non-translucent
// color above
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index ee865d3..7ccc250 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -523,15 +523,15 @@
|| mWindowsForAccessibilityObserver.size() > 0);
}
- void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ void setFullscreenMagnificationActivated(int displayId, boolean activated) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".setForceShowMagnifiableBounds",
- FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; show=" + show);
+ mAccessibilityTracing.logTrace(TAG + ".setFullscreenMagnificationActivated",
+ FLAGS_MAGNIFICATION_CALLBACK,
+ "displayId=" + displayId + "; activated=" + activated);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.setForceShowMagnifiableBounds(show);
- displayMagnifier.showMagnificationBoundsIfNeeded();
+ displayMagnifier.setFullscreenMagnificationActivated(activated);
}
}
@@ -624,10 +624,21 @@
private final long mLongAnimationDuration;
- private boolean mForceShowMagnifiableBounds = false;
+ private boolean mIsFullscreenMagnificationActivated = false;
+ private final Region mMagnificationRegion = new Region();
+ private final Region mOldMagnificationRegion = new Region();
private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
+ // Following fields are used for computing magnification region
+ private final Path mCircularPath;
+ private int mTempLayer = 0;
+ private final Point mScreenSize = new Point();
+ private final SparseArray<WindowState> mTempWindowStates =
+ new SparseArray<WindowState>();
+ private final RectF mTempRectF = new RectF();
+ private final Matrix mTempMatrix = new Matrix();
+
DisplayMagnifier(WindowManagerService windowManagerService,
DisplayContent displayContent,
Display display,
@@ -643,6 +654,15 @@
AccessibilityController.getAccessibilityControllerInternal(mService);
mLongAnimationDuration = mDisplayContext.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
+ if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
+ mCircularPath = new Path();
+
+ getDisplaySizeLocked(mScreenSize);
+ final int centerXY = mScreenSize.x / 2;
+ mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
+ } else {
+ mCircularPath = null;
+ }
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".DisplayMagnifier.constructor",
FLAGS_MAGNIFICATION_CALLBACK,
@@ -650,6 +670,7 @@
+ displayContent + "}; display={" + display + "}; callbacks={"
+ callbacks + "}");
}
+ recomputeBounds();
}
void setMagnificationSpec(MagnificationSpec spec) {
@@ -658,7 +679,7 @@
FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
}
updateMagnificationSpec(spec);
- mMagnifedViewport.recomputeBounds();
+ recomputeBounds();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
@@ -670,30 +691,26 @@
} else {
mMagnificationSpec.clear();
}
- // If this message is pending we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- mMagnifedViewport.setMagnifiedRegionBorderShown(
- isForceShowingMagnifiableBounds(), true);
- }
+
+ mMagnifedViewport.setShowMagnifiedBorderIfNeeded();
}
- void setForceShowMagnifiableBounds(boolean show) {
+ void setFullscreenMagnificationActivated(boolean activated) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
- FLAGS_MAGNIFICATION_CALLBACK, "show=" + show);
+ mAccessibilityTracing.logTrace(LOG_TAG + ".setFullscreenMagnificationActivated",
+ FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
}
- mForceShowMagnifiableBounds = show;
- mMagnifedViewport.setMagnifiedRegionBorderShown(show, true);
+ mIsFullscreenMagnificationActivated = activated;
+ mMagnifedViewport.setMagnifiedRegionBorderShown(activated, true);
+ mMagnifedViewport.showMagnificationBoundsIfNeeded();
}
- boolean isForceShowingMagnifiableBounds() {
+ boolean isFullscreenMagnificationActivated() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".isForceShowingMagnifiableBounds",
+ mAccessibilityTracing.logTrace(LOG_TAG + ".isFullscreenMagnificationActivated",
FLAGS_MAGNIFICATION_CALLBACK);
}
- return mForceShowMagnifiableBounds;
+ return mIsFullscreenMagnificationActivated;
}
void onWindowLayersChanged() {
@@ -704,7 +721,7 @@
if (DEBUG_LAYERS) {
Slog.i(LOG_TAG, "Layers changed.");
}
- mMagnifedViewport.recomputeBounds();
+ recomputeBounds();
mService.scheduleAnimationLocked();
}
@@ -718,6 +735,8 @@
Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
+ " displayId: " + displayContent.getDisplayId());
}
+
+ recomputeBounds();
mMagnifedViewport.onDisplaySizeChanged();
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
@@ -733,7 +752,7 @@
+ AppTransition.appTransitionOldToString(transition)
+ " displayId: " + displayId);
}
- final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
if (isMagnifierActivated) {
switch (transition) {
case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
@@ -758,7 +777,7 @@
Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type)
+ " displayId: " + displayId);
}
- final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
if (isMagnifierActivated) {
// All opening/closing situations.
switch (type) {
@@ -782,7 +801,7 @@
+ AppTransition.appTransitionOldToString(transition)
+ " displayId: " + windowState.getDisplayId());
}
- final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
final int type = windowState.mAttrs.type;
switch (transition) {
case WindowManagerPolicy.TRANSIT_ENTER:
@@ -835,9 +854,7 @@
}
void getMagnifiedFrameInContentCoords(Rect rect) {
- Region magnificationRegion = new Region();
- mMagnifedViewport.getMagnificationRegion(magnificationRegion);
- magnificationRegion.getBounds(rect);
+ mMagnificationRegion.getBounds(rect);
rect.offset((int) -mMagnificationSpec.offsetX, (int) -mMagnificationSpec.offsetY);
rect.scale(1.0f / mMagnificationSpec.scale);
}
@@ -872,8 +889,8 @@
"outMagnificationRegion={" + outMagnificationRegion + "}");
}
// Make sure we're working with the most current bounds
- mMagnifedViewport.recomputeBounds();
- mMagnifedViewport.getMagnificationRegion(outMagnificationRegion);
+ recomputeBounds();
+ outMagnificationRegion.set(mMagnificationRegion);
}
boolean isMagnifying() {
@@ -887,16 +904,6 @@
mMagnifedViewport.destroyWindow();
}
- // Can be called outside of a surface transaction
- void showMagnificationBoundsIfNeeded() {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
- FLAGS_MAGNIFICATION_CALLBACK);
- }
- mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
- .sendToTarget();
- }
-
void drawMagnifiedRegionBorderIfNeeded() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
@@ -905,26 +912,169 @@
mMagnifedViewport.drawWindowIfNeeded();
}
+ void recomputeBounds() {
+ getDisplaySizeLocked(mScreenSize);
+ final int screenWidth = mScreenSize.x;
+ final int screenHeight = mScreenSize.y;
+
+ mMagnificationRegion.set(0, 0, 0, 0);
+ final Region availableBounds = mTempRegion1;
+ availableBounds.set(0, 0, screenWidth, screenHeight);
+
+ if (mCircularPath != null) {
+ availableBounds.setPath(mCircularPath, availableBounds);
+ }
+
+ Region nonMagnifiedBounds = mTempRegion4;
+ nonMagnifiedBounds.set(0, 0, 0, 0);
+
+ SparseArray<WindowState> visibleWindows = mTempWindowStates;
+ visibleWindows.clear();
+ populateWindowsOnScreen(visibleWindows);
+
+ final int visibleWindowCount = visibleWindows.size();
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ WindowState windowState = visibleWindows.valueAt(i);
+ final int windowType = windowState.mAttrs.type;
+ if (isExcludedWindowType(windowType)
+ || ((windowState.mAttrs.privateFlags
+ & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
+ || ((windowState.mAttrs.privateFlags
+ & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
+ continue;
+ }
+
+ // Consider the touchable portion of the window
+ Matrix matrix = mTempMatrix;
+ populateTransformationMatrix(windowState, matrix);
+ Region touchableRegion = mTempRegion3;
+ windowState.getTouchableRegion(touchableRegion);
+ Rect touchableFrame = mTempRect1;
+ touchableRegion.getBounds(touchableFrame);
+ RectF windowFrame = mTempRectF;
+ windowFrame.set(touchableFrame);
+ windowFrame.offset(-windowState.getFrame().left,
+ -windowState.getFrame().top);
+ matrix.mapRect(windowFrame);
+ Region windowBounds = mTempRegion2;
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ // Only update new regions
+ Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
+ portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
+ portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
+ windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
+
+ if (windowState.shouldMagnify()) {
+ mMagnificationRegion.op(windowBounds, Region.Op.UNION);
+ mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
+ } else {
+ nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+ availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+ }
+
+ // If the navigation bar window doesn't have touchable region, count
+ // navigation bar insets into nonMagnifiedBounds. It happens when
+ // navigation mode is gestural.
+ if (isUntouchableNavigationBar(windowState, mTempRegion3)) {
+ final Rect navBarInsets = getSystemBarInsetsFrame(windowState);
+ nonMagnifiedBounds.op(navBarInsets, Region.Op.UNION);
+ availableBounds.op(navBarInsets, Region.Op.DIFFERENCE);
+ }
+
+ // Count letterbox into nonMagnifiedBounds
+ if (windowState.areAppWindowBoundsLetterboxed()) {
+ Region letterboxBounds = getLetterboxBounds(windowState);
+ nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
+ availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
+ }
+
+ // Update accounted bounds
+ Region accountedBounds = mTempRegion2;
+ accountedBounds.set(mMagnificationRegion);
+ accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
+ accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
+
+ if (accountedBounds.isRect()) {
+ Rect accountedFrame = mTempRect1;
+ accountedBounds.getBounds(accountedFrame);
+ if (accountedFrame.width() == screenWidth
+ && accountedFrame.height() == screenHeight) {
+ break;
+ }
+ }
+ }
+ visibleWindows.clear();
+
+ mMagnifedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
+
+
+ final boolean magnifiedChanged =
+ !mOldMagnificationRegion.equals(mMagnificationRegion);
+ if (magnifiedChanged) {
+ mMagnifedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
+
+ mOldMagnificationRegion.set(mMagnificationRegion);
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(mMagnificationRegion);
+ mHandler.obtainMessage(
+ MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
+ .sendToTarget();
+ }
+ }
+
+ private Region getLetterboxBounds(WindowState windowState) {
+ final ActivityRecord appToken = windowState.mActivityRecord;
+ if (appToken == null) {
+ return new Region();
+ }
+
+ final Rect boundsWithoutLetterbox = windowState.getBounds();
+ final Rect letterboxInsets = appToken.getLetterboxInsets();
+
+ final Rect boundsIncludingLetterbox = Rect.copyOrNull(boundsWithoutLetterbox);
+ // Letterbox insets from mActivityRecord are positive, so we negate them to grow the
+ // bounds to include the letterbox.
+ boundsIncludingLetterbox.inset(
+ Insets.subtract(Insets.NONE, Insets.of(letterboxInsets)));
+
+ final Region letterboxBounds = new Region();
+ letterboxBounds.set(boundsIncludingLetterbox);
+ letterboxBounds.op(boundsWithoutLetterbox, Region.Op.DIFFERENCE);
+ return letterboxBounds;
+ }
+
+ private boolean isExcludedWindowType(int windowType) {
+ return windowType == TYPE_MAGNIFICATION_OVERLAY
+ // Omit the touch region of window magnification to avoid the cut out of the
+ // magnification and the magnified center of window magnification could be
+ // in the bounds
+ || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+ }
+
+ private void populateWindowsOnScreen(SparseArray<WindowState> outWindows) {
+ mTempLayer = 0;
+ mDisplayContent.forAllWindows((w) -> {
+ if (w.isOnScreen() && w.isVisible()
+ && (w.mAttrs.alpha != 0)) {
+ mTempLayer++;
+ outWindows.put(mTempLayer, w);
+ }
+ }, /* traverseTopToBottom= */ false);
+ }
+
+ private void getDisplaySizeLocked(Point outSize) {
+ final Rect bounds =
+ mDisplayContent.getConfiguration().windowConfiguration.getBounds();
+ outSize.set(bounds.width(), bounds.height());
+ }
+
void dump(PrintWriter pw, String prefix) {
mMagnifedViewport.dump(pw, prefix);
}
private final class MagnifiedViewport {
- private final SparseArray<WindowState> mTempWindowStates =
- new SparseArray<WindowState>();
-
- private final RectF mTempRectF = new RectF();
-
- private final Point mScreenSize = new Point();
-
- private final Matrix mTempMatrix = new Matrix();
-
- private final Region mMagnificationRegion = new Region();
- private final Region mOldMagnificationRegion = new Region();
-
- private final Path mCircularPath;
-
private final float mBorderWidth;
private final int mHalfBorderWidth;
private final int mDrawBorderInset;
@@ -932,7 +1082,6 @@
private final ViewportWindow mWindow;
private boolean mFullRedrawNeeded;
- private int mTempLayer = 0;
MagnifiedViewport() {
mBorderWidth = mDisplayContext.getResources().getDimension(
@@ -940,186 +1089,59 @@
mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
mDrawBorderInset = (int) mBorderWidth / 2;
mWindow = new ViewportWindow(mDisplayContext);
+ }
- if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
- mCircularPath = new Path();
-
- getDisplaySizeLocked(mScreenSize);
- final int centerXY = mScreenSize.x / 2;
- mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
+ void updateBorderDrawingStatus(int screenWidth, int screenHeight) {
+ mWindow.setBounds(mMagnificationRegion);
+ final Rect dirtyRect = mTempRect1;
+ if (mFullRedrawNeeded) {
+ mFullRedrawNeeded = false;
+ dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+ screenWidth - mDrawBorderInset,
+ screenHeight - mDrawBorderInset);
+ mWindow.invalidate(dirtyRect);
} else {
- mCircularPath = null;
+ final Region dirtyRegion = mTempRegion3;
+ dirtyRegion.set(mMagnificationRegion);
+ dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
+ dirtyRegion.getBounds(dirtyRect);
+ mWindow.invalidate(dirtyRect);
}
-
- recomputeBounds();
}
- void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
- outMagnificationRegion.set(mMagnificationRegion);
+ void setShowMagnifiedBorderIfNeeded() {
+ // If this message is pending, we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ setMagnifiedRegionBorderShown(
+ isFullscreenMagnificationActivated(), true);
+ }
}
- void recomputeBounds() {
- getDisplaySizeLocked(mScreenSize);
- final int screenWidth = mScreenSize.x;
- final int screenHeight = mScreenSize.y;
-
- mMagnificationRegion.set(0, 0, 0, 0);
- final Region availableBounds = mTempRegion1;
- availableBounds.set(0, 0, screenWidth, screenHeight);
-
- if (mCircularPath != null) {
- availableBounds.setPath(mCircularPath, availableBounds);
+ // Can be called outside of a surface transaction
+ void showMagnificationBoundsIfNeeded() {
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
+ FLAGS_MAGNIFICATION_CALLBACK);
}
+ mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
+ .sendToTarget();
+ }
- Region nonMagnifiedBounds = mTempRegion4;
- nonMagnifiedBounds.set(0, 0, 0, 0);
-
- SparseArray<WindowState> visibleWindows = mTempWindowStates;
- visibleWindows.clear();
- populateWindowsOnScreen(visibleWindows);
-
- final int visibleWindowCount = visibleWindows.size();
- for (int i = visibleWindowCount - 1; i >= 0; i--) {
- WindowState windowState = visibleWindows.valueAt(i);
- final int windowType = windowState.mAttrs.type;
- if (isExcludedWindowType(windowType)
- || ((windowState.mAttrs.privateFlags
- & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
- || ((windowState.mAttrs.privateFlags
- & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
- continue;
- }
-
- // Consider the touchable portion of the window
- Matrix matrix = mTempMatrix;
- populateTransformationMatrix(windowState, matrix);
- Region touchableRegion = mTempRegion3;
- windowState.getTouchableRegion(touchableRegion);
- Rect touchableFrame = mTempRect1;
- touchableRegion.getBounds(touchableFrame);
- RectF windowFrame = mTempRectF;
- windowFrame.set(touchableFrame);
- windowFrame.offset(-windowState.getFrame().left,
- -windowState.getFrame().top);
- matrix.mapRect(windowFrame);
- Region windowBounds = mTempRegion2;
- windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom);
- // Only update new regions
- Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
- portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
- portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
- windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
-
- if (windowState.shouldMagnify()) {
- mMagnificationRegion.op(windowBounds, Region.Op.UNION);
- mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
- } else {
- nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
- availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
- }
-
- // If the navigation bar window doesn't have touchable region, count
- // navigation bar insets into nonMagnifiedBounds. It happens when
- // navigation mode is gestural.
- if (isUntouchableNavigationBar(windowState, mTempRegion3)) {
- final Rect navBarInsets = getSystemBarInsetsFrame(windowState);
- nonMagnifiedBounds.op(navBarInsets, Region.Op.UNION);
- availableBounds.op(navBarInsets, Region.Op.DIFFERENCE);
- }
-
- // Count letterbox into nonMagnifiedBounds
- if (windowState.areAppWindowBoundsLetterboxed()) {
- Region letterboxBounds = getLetterboxBounds(windowState);
- nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
- availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
- }
-
- // Update accounted bounds
- Region accountedBounds = mTempRegion2;
- accountedBounds.set(mMagnificationRegion);
- accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
- accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
-
- if (accountedBounds.isRect()) {
- Rect accountedFrame = mTempRect1;
- accountedBounds.getBounds(accountedFrame);
- if (accountedFrame.width() == screenWidth
- && accountedFrame.height() == screenHeight) {
- break;
- }
- }
- }
- visibleWindows.clear();
-
+ void intersectWithDrawBorderInset(int screenWidth, int screenHeight) {
mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
Region.Op.INTERSECT);
-
- final boolean magnifiedChanged =
- !mOldMagnificationRegion.equals(mMagnificationRegion);
- if (magnifiedChanged) {
- mWindow.setBounds(mMagnificationRegion);
- final Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset,
- screenHeight - mDrawBorderInset);
- mWindow.invalidate(dirtyRect);
- } else {
- final Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(mMagnificationRegion);
- dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
- }
-
- mOldMagnificationRegion.set(mMagnificationRegion);
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = Region.obtain(mMagnificationRegion);
- mHandler.obtainMessage(
- MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
- .sendToTarget();
- }
- }
-
- private Region getLetterboxBounds(WindowState windowState) {
- final ActivityRecord appToken = windowState.mActivityRecord;
- if (appToken == null) {
- return new Region();
- }
-
- final Rect boundsWithoutLetterbox = windowState.getBounds();
- final Rect letterboxInsets = appToken.getLetterboxInsets();
-
- final Rect boundsIncludingLetterbox = Rect.copyOrNull(boundsWithoutLetterbox);
- // Letterbox insets from mActivityRecord are positive, so we negate them to grow the
- // bounds to include the letterbox.
- boundsIncludingLetterbox.inset(
- Insets.subtract(Insets.NONE, Insets.of(letterboxInsets)));
-
- final Region letterboxBounds = new Region();
- letterboxBounds.set(boundsIncludingLetterbox);
- letterboxBounds.op(boundsWithoutLetterbox, Region.Op.DIFFERENCE);
- return letterboxBounds;
- }
-
- private boolean isExcludedWindowType(int windowType) {
- return windowType == TYPE_MAGNIFICATION_OVERLAY
- // Omit the touch region of window magnification to avoid the cut out of the
- // magnification and the magnified center of window magnification could be
- // in the bounds
- || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
}
void onDisplaySizeChanged() {
- // If we are showing the magnification border, hide it immediately so
+ // If fullscreen magnification is activated, hide the border immediately so
// the user does not see strange artifacts during display size changed caused by
// rotation or folding/unfolding the device. In the rotation case, the screenshot
// used for rotation already has the border. After the rotation is complete
// we will show the border.
- if (isForceShowingMagnifiableBounds()) {
+ if (isFullscreenMagnificationActivated()) {
setMagnifiedRegionBorderShown(false, false);
final long delay = (long) (mLongAnimationDuration
* mService.getWindowAnimationScaleLocked());
@@ -1127,7 +1149,6 @@
MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
mHandler.sendMessageDelayed(message, delay);
}
- recomputeBounds();
mWindow.updateSize();
}
@@ -1148,23 +1169,6 @@
mWindow.releaseSurface();
}
- private void populateWindowsOnScreen(SparseArray<WindowState> outWindows) {
- mTempLayer = 0;
- mDisplayContent.forAllWindows((w) -> {
- if (w.isOnScreen() && w.isVisible()
- && (w.mAttrs.alpha != 0)) {
- mTempLayer++;
- outWindows.put(mTempLayer, w);
- }
- }, false /* traverseTopToBottom */ );
- }
-
- private void getDisplaySizeLocked(Point outSize) {
- final Rect bounds =
- mDisplayContent.getConfiguration().windowConfiguration.getBounds();
- outSize.set(bounds.width(), bounds.height());
- }
-
void dump(PrintWriter pw, String prefix) {
mWindow.dump(pw, prefix);
}
@@ -1490,7 +1494,7 @@
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
synchronized (mService.mGlobalLock) {
- if (isForceShowingMagnifiableBounds()) {
+ if (isFullscreenMagnificationActivated()) {
mMagnifedViewport.setMagnifiedRegionBorderShown(true, true);
mService.scheduleAnimationLocked();
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f150039..56024f7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -303,6 +303,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ConstrainDisplayApisConfig;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -5009,7 +5010,7 @@
* method will be called at the proper time.
*/
final void deliverNewIntentLocked(int callingUid, Intent intent, NeededUriGrants intentGrants,
- String referrer, boolean isShareIdentityEnabled) {
+ String referrer, boolean isShareIdentityEnabled, int userId, int recipientAppId) {
IBinder callerToken = new Binder();
if (android.security.Flags.contentUriPermissionApis()) {
computeCallerInfo(callerToken, intent, callingUid, referrer, isShareIdentityEnabled);
@@ -5017,6 +5018,11 @@
// The activity now gets access to the data associated with this Intent.
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
getUriPermissionsLocked());
+ if (isShareIdentityEnabled && android.security.Flags.contentUriPermissionApis()) {
+ final PackageManagerInternal pmInternal = mAtmService.getPackageManagerInternalLocked();
+ pmInternal.grantImplicitAccess(userId, intent, recipientAppId /*recipient*/,
+ callingUid /*visible*/, true /*direct*/);
+ }
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
boolean unsent = true;
@@ -8943,6 +8949,15 @@
}
}
+ // Fixed orientation bounds are the same as its parent container, so clear the fixed
+ // orientation bounds. This can happen in close to square displays where the orientation
+ // is not respected with insets, but the display still matches or is less than the
+ // activity aspect ratio.
+ if (resolvedBounds.equals(parentBounds)) {
+ resolvedBounds.set(prevResolvedBounds);
+ return;
+ }
+
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index f83003d..62fb4bf 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.graphics.Rect;
import android.os.Environment;
import android.os.SystemProperties;
import android.os.Trace;
@@ -617,6 +618,12 @@
return mPersistInfoProvider.use16BitFormat();
}
+ @Override
+ protected Rect getLetterboxInsets(ActivityRecord topActivity) {
+ // Do not capture letterbox for ActivityRecord
+ return Letterbox.EMPTY_RECT;
+ }
+
@NonNull
private SparseArray<UserSavedFile> getUserFiles(int userId) {
if (mUserSavedFiles.get(userId) == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 2c49203..0c5c8ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -434,6 +434,9 @@
// Don't modify the client's object!
intent = new Intent(intent);
+ // Remove existing mismatch flag so it can be properly updated later
+ intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+
// Collect information about the target of the Intent.
ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i],
0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid,
@@ -639,6 +642,10 @@
return mPendingRemoteAnimationRegistry;
}
+ ActivityRecord getLastStartActivity() {
+ return mLastStarter != null ? mLastStarter.mStartActivity : null;
+ }
+
void dumpLastHomeActivityStartResult(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.print("mLastHomeActivityStartResult=");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cfd04950..63cdf0e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -713,9 +713,14 @@
try {
onExecutionStarted();
- // Refuse possible leaked file descriptors
- if (mRequest.intent != null && mRequest.intent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (mRequest.intent != null) {
+ // Refuse possible leaked file descriptors
+ if (mRequest.intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ // Remove existing mismatch flag so it can be properly updated later
+ mRequest.intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
final LaunchingState launchingState;
@@ -2910,7 +2915,9 @@
activity.logStartActivity(EventLogTags.WM_NEW_INTENT, activity.getTask());
activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, intentGrants,
- mStartActivity.launchedFromPackage, mStartActivity.mShareIdentity);
+ mStartActivity.launchedFromPackage, mStartActivity.mShareIdentity,
+ mStartActivity.mUserId,
+ UserHandle.getAppId(mStartActivity.info.applicationInfo.uid));
mIntentDelivered = true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3637ab1..ec3c122 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1317,9 +1317,13 @@
IBinder allowlistToken, Intent fillInIntent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle bOptions) {
enforceNotIsolatedCaller("startActivityIntentSender");
- // Refuse possible leaked file descriptors
- if (fillInIntent != null && fillInIntent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (fillInIntent != null) {
+ // Refuse possible leaked file descriptors
+ if (fillInIntent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
if (!(target instanceof PendingIntentRecord)) {
@@ -1363,6 +1367,8 @@
return false;
}
intent = new Intent(intent);
+ // Remove existing mismatch flag so it can be properly updated later
+ intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
// The caller is not allowed to change the data.
intent.setDataAndType(r.intent.getData(), r.intent.getType());
// And we are resetting to find the next component...
@@ -5554,7 +5560,7 @@
* Saves the current activity manager state and includes the saved state in the next dump of
* activity manager.
*/
- void saveANRState(String reason) {
+ void saveANRState(ActivityRecord activity, String reason) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new FastPrintWriter(sw, false, 1024);
pw.println(" ANR time: " + DateFormat.getDateTimeInstance().format(new Date()));
@@ -5562,14 +5568,25 @@
pw.println(" Reason: " + reason);
}
pw.println();
- getActivityStartController().dump(pw, " ", null);
- pw.println();
+ if (activity != null) {
+ final Task rootTask = activity.getRootTask();
+ if (rootTask != null) {
+ rootTask.forAllTaskFragments(
+ tf -> tf.dumpInner(" ", pw, true /* dumpAll */, null /* dumpPackage */));
+ pw.println();
+ }
+ mActivityStartController.dump(pw, " ", activity.packageName);
+ if (mActivityStartController.getLastStartActivity() != activity) {
+ activity.dump(pw, " ", true /* dumpAll */);
+ }
+ }
+ ActivityTaskSupervisor.printThisActivity(pw, mRootWindowContainer.getTopResumedActivity(),
+ null /* dumpPackage */, INVALID_DISPLAY, true /* needSep */,
+ " ResumedActivity: ", /* header= */ null /* header */);
+ mLockTaskController.dump(pw, " ");
+ mKeyguardController.dump(pw, " ");
pw.println("-------------------------------------------------------------------"
+ "------------");
- dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
- true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
- INVALID_DISPLAY, "" /* header */);
- pw.println();
pw.close();
mLastANRState = sw.toString();
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index b9f6e17..0013c5c 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -367,7 +367,7 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "dumpAnrStateLocked()");
synchronized (mService.mGlobalLock) {
mService.saveANRStateLocked(activity, windowState, reason);
- mService.mAtmService.saveANRState(reason);
+ mService.mAtmService.saveANRState(activity, reason);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c2dfa21..b51f899 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -35,6 +35,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.graphics.Point;
import android.graphics.Rect;
@@ -629,7 +630,7 @@
final ActivityRecord ar = openApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, true /* open */)) {
openApps.removeAt(i);
- mAnimationHandler.markStartingSurfaceMatch();
+ mAnimationHandler.markStartingSurfaceMatch(null /* reparentTransaction */);
}
}
for (int i = closeApps.size() - 1; i >= 0; --i) {
@@ -773,10 +774,15 @@
for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) {
final WindowContainer wc = mTmpOpenApps.get(i);
if (mAnimationHandler.isTarget(wc, true /* open */)) {
- mAnimationHandler.markStartingSurfaceMatch();
+ mAnimationHandler.markStartingSurfaceMatch(startTransaction);
break;
}
}
+ // release animation leash
+ if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) {
+ startTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
+ mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null;
+ }
// Because the target will reparent to transition root, so it cannot be controlled by
// animation leash. Hide the close target when transition starts.
startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
@@ -993,7 +999,7 @@
}
final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[2];
targets[0] = mCloseAdaptor.mAnimationTarget;
- targets[1] = mOpenAnimAdaptor.getOrCreateAnimationTarget();
+ targets[1] = mOpenAnimAdaptor.mRemoteAnimationTarget;
return targets;
}
@@ -1060,18 +1066,20 @@
if (mOpenActivities != null) {
for (int i = mOpenActivities.length - 1; i >= 0; --i) {
- if (mOpenActivities[i].mLaunchTaskBehind) {
- restoreLaunchBehind(mOpenActivities[i]);
+ final ActivityRecord resetActivity = mOpenActivities[i];
+ if (resetActivity.mLaunchTaskBehind) {
+ restoreLaunchBehind(resetActivity);
}
}
}
}
- void markStartingSurfaceMatch() {
- mStartingSurfaceTargetMatch = true;
- for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
- mOpenAnimAdaptor.mAdaptors[i].reparentWindowlessSurfaceToTarget();
+ void markStartingSurfaceMatch(SurfaceControl.Transaction reparentTransaction) {
+ if (mStartingSurfaceTargetMatch) {
+ return;
}
+ mStartingSurfaceTargetMatch = true;
+ mOpenAnimAdaptor.reparentWindowlessSurfaceToTarget(reparentTransaction);
}
void clearBackAnimateTarget() {
@@ -1140,14 +1148,23 @@
private static class BackWindowAnimationAdaptorWrapper {
final BackWindowAnimationAdaptor[] mAdaptors;
+ // The highest remote animation target, which can be a wrapper if multiple adaptors,
+ // or the single opening target.
+ final RemoteAnimationTarget mRemoteAnimationTarget;
SurfaceControl.Transaction mCloseTransaction;
+ // The starting surface task Id. Used to clear the starting surface if the animation has
+ // requested one during animating.
+ private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ private SurfaceControl mStartingSurface;
BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
@NonNull WindowContainer... targets) {
mAdaptors = new BackWindowAnimationAdaptor[targets.length];
for (int i = targets.length - 1; i >= 0; --i) {
mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType);
}
+ mRemoteAnimationTarget = targets.length > 1 ? createWrapTarget()
+ : mAdaptors[0].mAnimationTarget;
}
boolean isValid() {
@@ -1160,75 +1177,150 @@
}
void cleanUp(boolean startingSurfaceMatch) {
+ cleanUpWindowlessSurface(startingSurfaceMatch);
for (int i = mAdaptors.length - 1; i >= 0; --i) {
- mAdaptors[i].cleanUpWindowlessSurface(startingSurfaceMatch);
mAdaptors[i].mTarget.cancelAnimation();
}
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ mStartingSurface = null;
if (mCloseTransaction != null) {
mCloseTransaction.apply();
mCloseTransaction = null;
}
}
- void onAnimationFinish() {
- final SurfaceControl.Transaction pt = mAdaptors[0].mTarget.getPendingTransaction();
- if (mCloseTransaction != null) {
- pt.merge(mCloseTransaction);
- mCloseTransaction = null;
- }
- if (mAdaptors.length > 1) {
- for (int i = mAdaptors.length - 1; i >= 0; --i) {
- final WindowContainer wc = mAdaptors[i].mTarget;
- final WindowContainer parent = wc.getParent();
- if (parent != null) {
- pt.reparent(wc.getSurfaceControl(),
- parent.getSurfaceControl());
- }
- }
- }
- }
-
- @NonNull RemoteAnimationTarget getOrCreateAnimationTarget() {
+ private RemoteAnimationTarget createWrapTarget() {
// Special handle for opening two activities together.
// If we animate both activities separately, the animation area and rounded corner
// would also being handled separately. To make them seem like "open" together, wrap
// their leash with another animation leash.
- if (mAdaptors.length > 1 && mCloseTransaction == null) {
- final Rect unionBounds = new Rect();
- for (int i = mAdaptors.length - 1; i >= 0; --i) {
- unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
- }
- final WindowContainer wc = mAdaptors[0].mTarget;
- final Task task = wc.asActivityRecord() != null
- ? wc.asActivityRecord().getTask() : wc.asTask();
- final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
- final SurfaceControl leashSurface = new SurfaceControl.Builder()
- .setName("cross-animation-leash")
- .setContainerLayer()
- .setHidden(false)
- .setParent(task.getSurfaceControl())
- .build();
- final SurfaceControl.Transaction pt = wc.getPendingTransaction();
- pt.setLayer(leashSurface, wc.getParent().getLastLayer());
- mCloseTransaction = new SurfaceControl.Transaction();
- mCloseTransaction.reparent(leashSurface, null);
- for (int i = mAdaptors.length - 1; i >= 0; --i) {
- BackWindowAnimationAdaptor adaptor = mAdaptors[i];
- pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
- pt.setPosition(adaptor.mAnimationTarget.leash,
- adaptor.mAnimationTarget.localBounds.left,
- adaptor.mAnimationTarget.localBounds.top);
- }
- return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
- represent.isTranslucent, represent.clipRect, represent.contentInsets,
- represent.prefixOrderIndex,
- new Point(unionBounds.left, unionBounds.top),
- unionBounds, unionBounds, represent.windowConfiguration,
- true /* isNotInRecents */, null, null, represent.taskInfo,
- represent.allowEnterPip);
- } else {
- return mAdaptors[0].mAnimationTarget;
+ final Rect unionBounds = new Rect();
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
}
+ final WindowContainer wc = mAdaptors[0].mTarget;
+ final Task task = wc.asActivityRecord() != null
+ ? wc.asActivityRecord().getTask() : wc.asTask();
+ final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
+ final SurfaceControl leashSurface = new SurfaceControl.Builder()
+ .setName("cross-animation-leash")
+ .setContainerLayer()
+ .setHidden(false)
+ .setParent(task.getSurfaceControl())
+ .build();
+ mCloseTransaction = new SurfaceControl.Transaction();
+ mCloseTransaction.reparent(leashSurface, null);
+ final SurfaceControl.Transaction pt = wc.getPendingTransaction();
+ pt.setLayer(leashSurface, wc.getParent().getLastLayer());
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ BackWindowAnimationAdaptor adaptor = mAdaptors[i];
+ pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
+ pt.setPosition(adaptor.mAnimationTarget.leash,
+ adaptor.mAnimationTarget.localBounds.left,
+ adaptor.mAnimationTarget.localBounds.top);
+ // For adjacent activity embedded, reparent Activity to TaskFragment when
+ // animation finish
+ final WindowContainer parent = adaptor.mTarget.getParent();
+ if (parent != null) {
+ mCloseTransaction.reparent(adaptor.mTarget.getSurfaceControl(),
+ parent.getSurfaceControl());
+ }
+ }
+ return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
+ represent.isTranslucent, represent.clipRect, represent.contentInsets,
+ represent.prefixOrderIndex,
+ new Point(unionBounds.left, unionBounds.top),
+ unionBounds, unionBounds, represent.windowConfiguration,
+ true /* isNotInRecents */, null, null, represent.taskInfo,
+ represent.allowEnterPip);
+ }
+
+ void createStartingSurface(ActivityRecord[] visibleOpenActivities) {
+ if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
+ return;
+ }
+ final WindowContainer mainOpen = mAdaptors[0].mTarget;
+ final int switchType = mAdaptors[0].mSwitchType;
+ final Task openTask = switchType == TASK_SWITCH
+ ? mainOpen.asTask() : switchType == ACTIVITY_SWITCH
+ ? mainOpen.asActivityRecord().getTask() : null;
+ if (openTask == null) {
+ return;
+ }
+ final ActivityRecord mainActivity = switchType == ACTIVITY_SWITCH
+ ? mainOpen.asActivityRecord()
+ : openTask.getTopNonFinishingActivity();
+ if (mainActivity == null) {
+ return;
+ }
+ final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
+ // If there is only one adaptor, attach the windowless window to top activity,
+ // because fixed rotation only applies on activity.
+ // Note that embedded activity won't use fixed rotation.
+ final Configuration openConfig = mAdaptors.length == 1
+ ? mainActivity.getConfiguration() : openTask.getConfiguration();
+ mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
+ .addWindowlessStartingSurface(openTask, mainActivity,
+ mAdaptors.length == 1 ? mainActivity.getSurfaceControl()
+ : mRemoteAnimationTarget.leash, snapshot, openConfig,
+ new IWindowlessStartingSurfaceCallback.Stub() {
+ // Once the starting surface has been created in shell, it will call
+ // onSurfaceAdded to pass the created surface to core, so if a
+ // transition is triggered by the back gesture, there doesn't need to
+ // create another starting surface for the opening target, just reparent
+ // the starting surface to the opening target.
+ // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
+ // called, there won't be able to reparent the starting surface on
+ // opening target. But if that happens and transition target is matched,
+ // the app window should already draw.
+ @Override
+ public void onSurfaceAdded(SurfaceControl sc) {
+ synchronized (openTask.mWmService.mGlobalLock) {
+ if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
+ mStartingSurface = sc;
+ }
+ }
+ }
+ });
+ }
+
+ // When back gesture has triggered and transition target matches navigation target,
+ // reparent the starting surface to the opening target as it's starting window.
+ void reparentWindowlessSurfaceToTarget(SurfaceControl.Transaction reparentTransaction) {
+ if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ // If open target matches, reparent to open activity or task
+ if (mStartingSurface != null && mStartingSurface.isValid()) {
+ SurfaceControl.Transaction transaction = reparentTransaction != null
+ ? reparentTransaction : mAdaptors[0].mTarget.getPendingTransaction();
+ if (mAdaptors.length != 1) {
+ // More than one opening window, reparent starting surface to leaf task.
+ final WindowContainer wc = mAdaptors[0].mTarget;
+ final Task task = wc.asActivityRecord() != null
+ ? wc.asActivityRecord().getTask() : wc.asTask();
+ transaction.reparent(mStartingSurface, task != null
+ ? task.getSurfaceControl()
+ : mAdaptors[0].mTarget.getSurfaceControl());
+ }
+ // remove starting surface.
+ mStartingSurface = null;
+ }
+ }
+
+ /**
+ * Ask shell to clear the starting surface.
+ * @param openTransitionMatch if true, shell will play the remove starting window
+ * animation, otherwise remove it directly.
+ */
+ void cleanUpWindowlessSurface(boolean openTransitionMatch) {
+ if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ mAdaptors[0].mTarget.mWmService.mAtmService.mTaskOrganizerController
+ .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
+ !openTransitionMatch);
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
}
}
@@ -1240,11 +1332,6 @@
private RemoteAnimationTarget mAnimationTarget;
private final int mSwitchType;
- // The starting surface task Id. Used to clear the starting surface if the animation has
- // requested one during animating.
- private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
- private SurfaceControl mStartingSurface;
-
BackWindowAnimationAdaptor(@NonNull WindowContainer target, boolean isOpen,
int switchType) {
mBounds.set(target.getBounds());
@@ -1276,8 +1363,6 @@
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mCapturedLeash == animationLeash) {
mCapturedLeash = null;
- mRequestedStartingSurfaceId = INVALID_TASK_ID;
- mStartingSurface = null;
}
}
@@ -1345,84 +1430,6 @@
r.checkEnterPictureInPictureAppOpsState());
return mAnimationTarget;
}
-
- void createStartingSurface(@NonNull WindowContainer closeWindow,
- @NonNull ActivityRecord[] visibleOpenActivities) {
- if (!mIsOpen) {
- return;
- }
- if (mSwitchType == DIALOG_CLOSE) {
- return;
- }
- final Task openTask = mSwitchType == TASK_SWITCH
- ? mTarget.asTask() : mSwitchType == ACTIVITY_SWITCH
- ? mTarget.asActivityRecord().getTask() : null;
- if (openTask == null) {
- return;
- }
- final ActivityRecord mainActivity = mSwitchType == ACTIVITY_SWITCH
- ? mTarget.asActivityRecord()
- : openTask.getTopNonFinishingActivity();
- if (mainActivity == null) {
- return;
- }
- final TaskSnapshot snapshot = getSnapshot(mTarget, visibleOpenActivities);
- mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
- .addWindowlessStartingSurface(openTask, mainActivity,
- // Choose configuration from closeWindow, because the configuration
- // of opening target may not update before resume, so the starting
- // surface should occlude it entirely.
- mAnimationTarget.leash, snapshot, closeWindow.getConfiguration(),
- new IWindowlessStartingSurfaceCallback.Stub() {
- // Once the starting surface has been created in shell, it will call
- // onSurfaceAdded to pass the created surface to core, so if a
- // transition is triggered by the back gesture, there doesn't need to
- // create another starting surface for the opening target, just reparent
- // the starting surface to the opening target.
- // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
- // called, there won't be able to reparent the starting surface on
- // opening target. But if that happens and transition target is matched,
- // the app window should already draw.
- @Override
- public void onSurfaceAdded(SurfaceControl sc) {
- synchronized (mTarget.mWmService.mGlobalLock) {
- if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
- mStartingSurface = sc;
- }
- }
- }
- });
- }
-
- // When back gesture has triggered and transition target matches navigation target,
- // reparent the starting surface to the opening target as it's starting window.
- void reparentWindowlessSurfaceToTarget() {
- if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
- return;
- }
- // If open target matches, reparent to open activity or task
- if (mStartingSurface != null && mStartingSurface.isValid()) {
- mTarget.getPendingTransaction()
- .reparent(mStartingSurface, mTarget.getSurfaceControl());
- // remove starting surface.
- mStartingSurface = null;
- }
- }
-
- /**
- * Ask shell to clear the starting surface.
- * @param openTransitionMatch if true, shell will play the remove starting window
- * animation, otherwise remove it directly.
- */
- void cleanUpWindowlessSurface(boolean openTransitionMatch) {
- if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
- return;
- }
- mTarget.mWmService.mAtmService.mTaskOrganizerController
- .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
- !openTransitionMatch);
- mRequestedStartingSurfaceId = INVALID_TASK_ID;
- }
}
ScheduleAnimationBuilder prepareAnimation(
@@ -1493,24 +1500,15 @@
/**
* Apply preview strategy on the opening target
- * @param closeWindow The close window, where it's configuration should cover all
- * open target(s).
+ *
* @param openAnimationAdaptor The animator who can create starting surface.
* @param visibleOpenActivities The visible activities in opening targets.
*/
- private void applyPreviewStrategy(@NonNull WindowContainer closeWindow,
- @NonNull BackWindowAnimationAdaptor[] openAnimationAdaptor,
+ private void applyPreviewStrategy(
+ @NonNull BackWindowAnimationAdaptorWrapper openAnimationAdaptor,
@NonNull ActivityRecord[] visibleOpenActivities) {
- if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind
- // TODO (b/274997067) Draw two snapshot in a single starting surface.
- // We are using TaskId as the key of
- // StartingSurfaceDrawer#StartingWindowRecordManager, so we cannot create
- // two activity snapshot with WindowlessStartingWindow.
- // Try to draw two snapshot within a WindowlessStartingWindow, or find
- // another key for StartingWindowRecordManager.
- && openAnimationAdaptor.length == 1) {
- openAnimationAdaptor[0].createStartingSurface(closeWindow,
- visibleOpenActivities);
+ if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
+ openAnimationAdaptor.createStartingSurface(visibleOpenActivities);
} else {
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
setLaunchBehind(visibleOpenActivities[i]);
@@ -1541,7 +1539,7 @@
}
mCloseTarget.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
- applyPreviewStrategy(mCloseTarget, mOpenAnimAdaptor.mAdaptors, openingActivities);
+ applyPreviewStrategy(mOpenAnimAdaptor, openingActivities);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1565,7 +1563,6 @@
// animation was canceled
return;
}
- mOpenAnimAdaptor.onAnimationFinish();
if (!triggerBack) {
clearBackAnimateTarget();
} else {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 86ca1ea..fdae53f 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -94,6 +94,11 @@
private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS;
private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
private static final int NO_PROCESS_UID = -1;
+
+ 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";
+
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
@@ -232,9 +237,9 @@
private final boolean mCallingUidHasAnyVisibleWindow;
private final @ActivityManager.ProcessState int mCallingUidProcState;
private final boolean mIsCallingUidPersistentSystemProcess;
- private final BackgroundStartPrivileges mBalAllowedByPiSender;
- private final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
- private final BackgroundStartPrivileges mBalAllowedByPiCreator;
+ final BackgroundStartPrivileges mBalAllowedByPiSender;
+ final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
+ final BackgroundStartPrivileges mBalAllowedByPiCreator;
private final String mRealCallingPackage;
private final int mRealCallingUid;
private final int mRealCallingPid;
@@ -248,11 +253,12 @@
private final WindowProcessController mRealCallerApp;
private final boolean mIsCallForResult;
private final ActivityOptions mCheckedOptions;
- private final String mAutoOptInReason;
+ final String mAutoOptInReason;
+ private final boolean mAutoOptInCaller;
private BalVerdict mResultForCaller;
private BalVerdict mResultForRealCaller;
- private BalState(int callingUid, int callingPid, final String callingPackage,
+ @VisibleForTesting BalState(int callingUid, int callingPid, final String callingPackage,
int realCallingUid, int realCallingPid,
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
@@ -280,26 +286,27 @@
if (!balImproveRealCallerVisibilityCheck()) {
// without this fix the auto-opt ins below would violate CTS tests
mAutoOptInReason = null;
- } else if (mIsCallForResult) {
- mAutoOptInReason = "callForResult";
+ mAutoOptInCaller = false;
} else if (originatingPendingIntent == null) {
- mAutoOptInReason = "notPendingIntent";
+ mAutoOptInReason = AUTO_OPT_IN_NOT_PENDING_INTENT;
+ mAutoOptInCaller = true;
+ } else if (mIsCallForResult) {
+ mAutoOptInReason = AUTO_OPT_IN_CALL_FOR_RESULT;
+ mAutoOptInCaller = false;
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
- mAutoOptInReason = "sameUid";
+ mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
+ mAutoOptInCaller = false;
} else {
mAutoOptInReason = null;
+ mAutoOptInCaller = false;
}
- if (mAutoOptInReason != null) {
+ if (mAutoOptInCaller) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
- mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
- == MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE
- : BackgroundStartPrivileges.ALLOW_BAL;
} else {
// for PendingIntents we restrict BAL based on target_sdk
mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
@@ -312,10 +319,21 @@
mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
? mBalAllowedByPiCreatorWithHardening
: mBalAllowedByPiCreatorWithoutHardening;
+ }
+
+ if (mAutoOptInReason != null) {
+ // grant BAL privileges unless explicitly opted out
+ mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE
+ : BackgroundStartPrivileges.ALLOW_BAL;
+ } else {
+ // for PendingIntents we restrict BAL based on target_sdk
mBalAllowedByPiSender =
PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
checkedOptions, realCallingUid, mRealCallingPackage);
}
+
mAppSwitchState = mService.getBalAppSwitchesState();
mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
mIsCallingUidPersistentSystemProcess =
@@ -407,7 +425,7 @@
return mRealCallingUid != NO_PROCESS_UID;
}
- private boolean isPendingIntent() {
+ boolean isPendingIntent() {
return mOriginatingPendingIntent != null && hasRealCaller();
}
@@ -485,23 +503,19 @@
}
public boolean callerExplicitOptInOrAutoOptIn() {
- if (mAutoOptInReason == null) {
- return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ if (mAutoOptInCaller) {
+ return !callerExplicitOptOut();
}
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
}
public boolean realCallerExplicitOptInOrAutoOptIn() {
- if (mAutoOptInReason == null) {
- return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ if (mAutoOptInReason != null) {
+ return !realCallerExplicitOptOut();
}
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
}
public boolean callerExplicitOptOut() {
@@ -523,6 +537,11 @@
return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
!= MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
}
+
+ @Override
+ public String toString() {
+ return dump();
+ }
}
static class BalVerdict {
@@ -972,7 +991,7 @@
* String, int, boolean, boolean, boolean, long, long, long)} for details on the
* exceptions.
*/
- private BalVerdict checkProcessAllowsBal(WindowProcessController app,
+ @VisibleForTesting BalVerdict checkProcessAllowsBal(WindowProcessController app,
BalState state) {
if (app == null) {
return BalVerdict.BLOCK;
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 5b4fb3e..e48e4e8 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -87,11 +87,7 @@
void scheduleTransactionItemNow(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
- } else {
- clientTransaction.addCallback(transactionItem);
- }
+ clientTransaction.addTransactionItem(transactionItem);
scheduleTransaction(clientTransaction);
}
@@ -115,11 +111,8 @@
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
- } else {
- clientTransaction.addCallback(transactionItem);
- }
+ clientTransaction.addTransactionItem(transactionItem);
+
scheduleTransaction(clientTransaction);
}
}
@@ -160,8 +153,8 @@
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- clientTransaction.addCallback(transactionItem);
- clientTransaction.setLifecycleStateRequest(lifecycleItem);
+ clientTransaction.addTransactionItem(transactionItem);
+ clientTransaction.addTransactionItem(lifecycleItem);
scheduleTransaction(clientTransaction);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a7bbc25..5cf9acd 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -104,7 +104,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayInfo;
@@ -2070,8 +2069,7 @@
}
return false;
}
- if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve()
- && !mDisplayContent.isSleeping()) {
+ if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve() && mScreenOnFully) {
mCachedDecorInsets = null;
}
mDecorInsets.invalidate();
@@ -2136,16 +2134,6 @@
}
mCachedDecorInsets.mPreserveId =
mDisplayContent.mTransitionController.getCollectingTransitionId();
- // The validator will run after the transition is finished. So if the insets are changed
- // during the transition, it can update to the latest state.
- mDisplayContent.mTransitionController.mStateValidators.add(() -> {
- // The insets provider client may defer to change its window until screen is on. So
- // only validate when awake to avoid the cache being always dropped.
- if (!mDisplayContent.isSleeping() && updateDecorInsetsInfo()) {
- Slog.d(TAG, "Insets changed after display switch transition");
- mDisplayContent.sendNewConfiguration();
- }
- });
}
@NavigationBarPosition
@@ -2891,9 +2879,6 @@
if (!CLIENT_TRANSIENT) {
mSystemGestures.dump(pw, prefix);
}
-
- pw.print(prefix); pw.println("Looper state:");
- mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
}
private boolean supportsPointerLocation() {
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index fac62fc..0978cb4 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -184,7 +184,7 @@
if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
return false;
}
- return mInputManagerService.transferTouchFocus(ew.getInputChannelToken(),
+ return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
transferToHostWindowState.mInputChannelToken);
}
@@ -194,7 +194,7 @@
if (!isValidTouchGestureParams(hostWindowState, ew)) {
return false;
}
- return mInputManagerService.transferTouchFocus(hostWindowState.mInputChannelToken,
+ return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index e66321a..362d4ef 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -49,7 +49,7 @@
*/
public class Letterbox {
- private static final Rect EMPTY_RECT = new Rect();
+ static final Rect EMPTY_RECT = new Rect();
private static final Point ZERO_POINT = new Point(0, 0);
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5243527..5d613cf 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -52,7 +52,6 @@
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
@@ -1369,23 +1368,25 @@
*
* <p>Conditions that needs to be met:
* <ul>
- * <li>Activity is portrait-only.
- * <li>Fullscreen window in landscape device orientation.
+ * <li>Windowing mode is fullscreen.
* <li>Horizontal Reachability is enabled.
- * <li>Activity fills parent vertically.
+ * <li>First top opaque activity fills parent vertically, but not horizontally.
* </ul>
*/
private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
// Use screen resolved bounds which uses resolved bounds or size compat bounds
// as activity bounds can sometimes be empty
+ final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior()
+ ? mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
+ : mActivityRecord.getScreenResolvedBounds();
return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
- && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
- && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT)
// Check whether the activity fills the parent vertically.
&& parentConfiguration.windowConfiguration.getAppBounds().height()
- <= mActivityRecord.getScreenResolvedBounds().height();
+ <= opaqueActivityBounds.height()
+ && parentConfiguration.windowConfiguration.getAppBounds().width()
+ > opaqueActivityBounds.width();
}
@VisibleForTesting
@@ -1402,23 +1403,25 @@
*
* <p>Conditions that needs to be met:
* <ul>
- * <li>Activity is landscape-only.
- * <li>Fullscreen window in portrait device orientation.
+ * <li>Windowing mode is fullscreen.
* <li>Vertical Reachability is enabled.
- * <li>Activity fills parent horizontally.
+ * <li>First top opaque activity fills parent horizontally but not vertically.
* </ul>
*/
private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
// Use screen resolved bounds which uses resolved bounds or size compat bounds
// as activity bounds can sometimes be empty
+ final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior()
+ ? mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
+ : mActivityRecord.getScreenResolvedBounds();
return mLetterboxConfiguration.getIsVerticalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
- && (parentConfiguration.orientation == ORIENTATION_PORTRAIT
- && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE)
// Check whether the activity fills the parent horizontally.
- && parentConfiguration.windowConfiguration.getBounds().width()
- == mActivityRecord.getScreenResolvedBounds().width();
+ && parentConfiguration.windowConfiguration.getAppBounds().width()
+ <= opaqueActivityBounds.width()
+ && parentConfiguration.windowConfiguration.getAppBounds().height()
+ > opaqueActivityBounds.height();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 19a9b3f..b562ccf 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2481,6 +2481,7 @@
if (displayShouldSleep == display.isSleeping()) {
continue;
}
+ final boolean wasSleeping = display.isSleeping();
display.setIsSleeping(displayShouldSleep);
if (display.mTransitionController.isShellTransitionsEnabled()
@@ -2506,9 +2507,7 @@
// Use NONE if keyguard is not showing.
int transit = TRANSIT_NONE;
Task startTask = null;
- if (!display.getDisplayPolicy().isAwake()) {
- // Note that currently this only happens on default display because non-default
- // display is always awake.
+ if (wasSleeping) {
transit = TRANSIT_WAKE;
} else if (display.isKeyguardOccluded()) {
// The display was awake so this is resuming activity for occluding keyguard.
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 975208f..908cbd3 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -110,9 +110,7 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- // Set of visible application overlay window surfaces connected to this session.
- private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
- // Set of visible alert window surfaces connected to this session.
+ /** Set of visible alert/app-overlay window surfaces connected to this session. */
private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
@@ -796,46 +794,45 @@
}
boolean changed;
-
- if (!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay) {
- // We want to track non-system apps adding alert windows so we can post an
- // on-going notification for the user to control their visibility.
- if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
- MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type, true);
- } else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
- MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type, true);
+ // Track non-system apps adding overlay/alert windows, so a notification can post for the
+ // user to control their visibility.
+ final boolean noSystemOverlayPermission =
+ !mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
+ if (visible) {
+ changed = mAlertWindowSurfaces.add(surfaceController);
+ if (type == TYPE_APPLICATION_OVERLAY) {
+ MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
+ false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
+ } else if (noSystemOverlayPermission) {
+ MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
+ true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
+ } else {
+ changed = mAlertWindowSurfaces.remove(surfaceController);
+ if (type == TYPE_APPLICATION_OVERLAY) {
+ MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
+ false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
+ } else if (noSystemOverlayPermission) {
+ MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
+ true /* only log for non-TYPE_APPLICATION_OVERLAY */);
+ }
+ }
- if (changed) {
- if (mAlertWindowSurfaces.isEmpty()) {
- cancelAlertWindowNotification();
- } else if (mAlertWindowNotification == null){
- mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
- if (mShowingAlertWindowNotificationAllowed) {
- mAlertWindowNotification.post();
- }
+ if (changed && noSystemOverlayPermission) {
+ if (mAlertWindowSurfaces.isEmpty()) {
+ cancelAlertWindowNotification();
+ } else if (mAlertWindowNotification == null) {
+ mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
+ if (mShowingAlertWindowNotificationAllowed) {
+ mAlertWindowNotification.post();
}
}
}
- if (type != TYPE_APPLICATION_OVERLAY) {
- return;
- }
-
- if (visible) {
- changed = mAppOverlaySurfaces.add(surfaceController);
- MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type, false);
- } else {
- changed = mAppOverlaySurfaces.remove(surfaceController);
- MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type, false);
- }
-
- if (changed) {
- // Notify activity manager of changes to app overlay windows so it can adjust the
- // importance score for the process.
- setHasOverlayUi(!mAppOverlaySurfaces.isEmpty());
+ if (changed && mPid != WindowManagerService.MY_PID) {
+ // Notify activity manager that the process contains overlay/alert windows, so it can
+ // adjust the importance score for the process.
+ setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
}
}
@@ -870,12 +867,12 @@
mSurfaceSession = null;
mAddedWindows.clear();
mAlertWindowSurfaces.clear();
- mAppOverlaySurfaces.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
- private void setHasOverlayUi(boolean hasOverlayUi) {
+ @VisibleForTesting
+ void setHasOverlayUi(boolean hasOverlayUi) {
mService.mH.obtainMessage(H.SET_HAS_OVERLAY_UI, mPid, hasOverlayUi ? 1 : 0).sendToTarget();
}
@@ -890,7 +887,6 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 838ce86..10cbc66 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1590,7 +1590,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, activityResultItem);
} else {
- transaction.addCallback(activityResultItem);
+ transaction.addTransactionItem(activityResultItem);
}
}
}
@@ -1602,7 +1602,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, newIntentItem);
} else {
- transaction.addCallback(newIntentItem);
+ transaction.addTransactionItem(newIntentItem);
}
}
@@ -1624,7 +1624,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, resumeActivityItem);
} else {
- transaction.setLifecycleStateRequest(resumeActivityItem);
+ transaction.addTransactionItem(resumeActivityItem);
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 55a3dec..6f548ab 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -198,14 +198,14 @@
// resizing/scrolling are not sent to the app. 'win' is the main window
// of the app, it may not have focus since there might be other windows
// on top (eg. a dialog window).
- WindowState transferFocusFromWin = win;
+ WindowState transferTouchFromWin = win;
if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win
&& displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) {
- transferFocusFromWin = displayContent.mCurrentFocus;
+ transferTouchFromWin = displayContent.mCurrentFocus;
}
- if (!mService.mInputManager.transferTouchFocus(
- transferFocusFromWin.mInputChannel, mTaskPositioner.mClientChannel,
- false /* isDragDrop */)) {
+ if (!mService.mInputManager.transferTouchGesture(
+ transferTouchFromWin.mInputChannel.getToken(),
+ mTaskPositioner.mClientChannel.getToken())) {
Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
cleanUpTaskPositioner();
return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 8b622d2..4218f8f 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -270,6 +270,11 @@
return source.getTaskDescription();
}
+ @Override
+ protected Rect getLetterboxInsets(ActivityRecord topActivity) {
+ return topActivity.getLetterboxInsets();
+ }
+
void getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks) {
// Since RecentsAnimation will handle task snapshot while switching apps with the
// best capture timing (e.g. IME window capture),
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 70775530..503f925 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -267,7 +267,8 @@
mSyncEngine.addOnIdleListener(this::tryStartCollectFromQueue);
}
- private void detachPlayer() {
+ @VisibleForTesting
+ void detachPlayer() {
if (mTransitionPlayer == null) return;
// Immediately set to null so that nothing inadvertently starts/queues.
mTransitionPlayer = null;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 669c61c..f3bb373 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -315,8 +315,7 @@
InputChannel source) {
return state.register(display)
.thenApply(unused ->
- service.transferTouchFocus(source, state.getInputChannel(),
- true /* isDragDrop */));
+ service.startDragAndDrop(source, state.getInputChannel()));
}
/**
@@ -409,13 +408,12 @@
public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec);
/**
- * Set by the accessibility framework to indicate whether the magnifiable regions of the display
- * should be shown.
+ * Set by the accessibility framework to indicate whether fullscreen magnification is activated.
*
* @param displayId The logical display id.
- * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
+ * @param activated The activation of fullscreen magnification
*/
- public abstract void setForceShowMagnifiableBounds(int displayId, boolean show);
+ public abstract void setFullscreenMagnificationActivated(int displayId, boolean activated);
/**
* Obtains the magnification regions.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e538f5d5..8ebe826 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6750,11 +6750,6 @@
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
- dumpWindowsNoHeaderLocked(pw, dumpAll, windows);
- }
-
- private void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll,
- ArrayList<WindowState> windows) {
mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
if (!mHidingNonSystemOverlayWindows.isEmpty()) {
@@ -6989,9 +6984,15 @@
if (reason != null) {
pw.println(" Reason: " + reason);
}
+ pw.println();
+ final ArrayList<WindowState> relatedWindows = new ArrayList<>();
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
final DisplayContent dc = mRoot.getChildAt(i);
final int displayId = dc.getDisplayId();
+ final WindowState currentFocus = dc.mCurrentFocus;
+ final ActivityRecord focusedApp = dc.mFocusedApp;
+ pw.println(" Display #" + displayId + " currentFocus=" + currentFocus
+ + " focusedApp=" + focusedApp);
if (!dc.mWinAddedSinceNullFocus.isEmpty()) {
pw.println(" Windows added in display #" + displayId + " since null focus: "
+ dc.mWinAddedSinceNullFocus);
@@ -7000,12 +7001,25 @@
pw.println(" Windows removed in display #" + displayId + " since null focus: "
+ dc.mWinRemovedSinceNullFocus);
}
+ pw.println(" Tasks in top down Z order:");
+ dc.forAllTaskDisplayAreas(tda -> {
+ tda.dump(pw, " ", false /* dumpAll */);
+ });
+ dc.getInputMonitor().dump(pw, " ");
+ pw.println();
+ dc.forAllWindows(w -> {
+ if ((currentFocus != null && Objects.equals(w.mAttrs.packageName,
+ currentFocus.mAttrs.packageName)) || (focusedApp != null
+ && Objects.equals(w.mAttrs.packageName, focusedApp.packageName))) {
+ relatedWindows.add(w);
+ }
+ }, true /* traverseTopToBottom */);
}
+ if (windowState != null && !relatedWindows.contains(windowState)) {
+ relatedWindows.add(windowState);
+ }
+ mRoot.dumpWindowsNoHeader(pw, true /* dumpAll */, relatedWindows);
pw.println();
- dumpWindowsNoHeaderLocked(pw, true, null);
- pw.println();
- pw.println("Last ANR continued");
- mRoot.dumpDisplayContents(pw);
pw.close();
mLastANRState = sw.toString();
@@ -7844,10 +7858,11 @@
}
@Override
- public void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ public void setFullscreenMagnificationActivated(int displayId, boolean activated) {
synchronized (mGlobalLock) {
if (mAccessibilityController.hasCallbacks()) {
- mAccessibilityController.setForceShowMagnifiableBounds(displayId, show);
+ mAccessibilityController
+ .setFullscreenMagnificationActivated(displayId, activated);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b8f1d15..934f8a7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2603,7 +2603,11 @@
/**
* Move the touch gesture from the currently touched window on this display to this window.
+ *
+ * @deprecated Use {@link
+ * com.android.server.input.InputManagerInternal#transferTouchGesture(IBinder, IBinder)}.
*/
+ @Deprecated
public boolean transferTouch() {
return mWmService.mInputManager.transferTouch(mInputChannelToken, getDisplayId());
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index cbbcd96..d821625 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -2186,9 +2186,9 @@
im->setSystemUiLightsOut(lightsOut);
}
-static jboolean nativeTransferTouchFocus(JNIEnv* env, jobject nativeImplObj,
- jobject fromChannelTokenObj, jobject toChannelTokenObj,
- jboolean isDragDrop) {
+static jboolean nativeTransferTouchGesture(JNIEnv* env, jobject nativeImplObj,
+ jobject fromChannelTokenObj, jobject toChannelTokenObj,
+ jboolean isDragDrop) {
if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
return JNI_FALSE;
}
@@ -2197,21 +2197,22 @@
sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (im->getInputManager()->getDispatcher().transferTouchFocus(fromChannelToken, toChannelToken,
- isDragDrop)) {
+ if (im->getInputManager()->getDispatcher().transferTouchGesture(fromChannelToken,
+ toChannelToken, isDragDrop)) {
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
-static jboolean nativeTransferTouch(JNIEnv* env, jobject nativeImplObj, jobject destChannelTokenObj,
- jint displayId) {
+static jboolean nativeTransferTouchOnDisplay(JNIEnv* env, jobject nativeImplObj,
+ jobject destChannelTokenObj, jint displayId) {
sp<IBinder> destChannelToken = ibinderForJavaObject(env, destChannelTokenObj);
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (im->getInputManager()->getDispatcher().transferTouch(destChannelToken,
- static_cast<int32_t>(displayId))) {
+ if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken,
+ static_cast<int32_t>(
+ displayId))) {
return JNI_TRUE;
} else {
return JNI_FALSE;
@@ -2875,9 +2876,9 @@
{"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
{"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
{"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
- {"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
- (void*)nativeTransferTouchFocus},
- {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
+ {"transferTouchGesture", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
+ (void*)nativeTransferTouchGesture},
+ {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouchOnDisplay},
{"getMousePointerSpeed", "()I", (void*)nativeGetMousePointerSpeed},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 17638fc..dc8cec9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -21,8 +21,6 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST;
import static android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST;
-import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
-import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
@@ -41,6 +39,7 @@
import android.app.admin.PasswordPolicy;
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.Flags;
import android.graphics.Color;
import android.net.wifi.WifiSsid;
import android.os.Bundle;
@@ -1297,7 +1296,7 @@
pw.print("encryptionRequested=");
pw.println(encryptionRequested);
- if (!dumpsysPolicyEngineMigrationEnabled()) {
+ if (!Flags.dumpsysPolicyEngineMigrationEnabled()) {
pw.print("disableCamera=");
pw.println(disableCamera);
@@ -1316,7 +1315,8 @@
UserRestrictionsUtils.dumpRestrictions(pw, " ", userRestrictions);
}
- if (!policyEngineMigrationV2Enabled() || !dumpsysPolicyEngineMigrationEnabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()
+ || !Flags.dumpsysPolicyEngineMigrationEnabled()) {
pw.print("mUsbDataSignaling=");
pw.println(mUsbDataSignalingEnabled);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 3e066f2..12f4407 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -24,7 +24,6 @@
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
import android.Manifest;
@@ -42,6 +41,7 @@
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -225,7 +225,7 @@
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -350,7 +350,7 @@
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -496,7 +496,7 @@
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -568,7 +568,7 @@
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1892,7 +1892,7 @@
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
@@ -1916,7 +1916,7 @@
private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return;
}
serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2081,7 +2081,7 @@
private void readMaxPolicySizeInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return;
}
mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9d84d6f..9c48f29 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -239,14 +239,6 @@
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
-import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
-import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled;
-import static android.app.admin.flags.Flags.permissionMigrationForZeroTrustImplEnabled;
-import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
-import static android.app.admin.flags.Flags.assistContentUserRestrictionEnabled;
-import static android.app.admin.flags.Flags.securityLogV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -272,6 +264,7 @@
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
@@ -360,6 +353,7 @@
import android.app.admin.UnsafeStateException;
import android.app.admin.UserRestrictionPolicyKey;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.Flags;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
@@ -513,7 +507,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.ProxyUtils;
-import com.android.net.thread.flags.Flags;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
@@ -2728,7 +2721,7 @@
return;
}
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
boolean auditLoggingEnabled = Boolean.TRUE.equals(
mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
@@ -3418,7 +3411,7 @@
@GuardedBy("getLockObject()")
private void maybeMigrateSecurityLoggingPolicyLocked() {
- if (!securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
+ if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
return;
}
@@ -3522,7 +3515,7 @@
}
revertTransferOwnershipIfNecessaryLocked();
- if (!policyEngineMigrationV2Enabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()) {
updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
}
}
@@ -11151,7 +11144,7 @@
pw.println();
mStatLogger.dump(pw);
pw.println();
- if (dumpsysPolicyEngineMigrationEnabled()) {
+ if (Flags.dumpsysPolicyEngineMigrationEnabled()) {
mDevicePolicyEngine.dump(pw);
pw.println();
}
@@ -12068,7 +12061,7 @@
}
if (packageList != null) {
- if (!devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingEnabled()) {
for (String pkg : packageList) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -12313,7 +12306,7 @@
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
- if (headlessDeviceOwnerSingleUserEnabled()) {
+ if (Flags.headlessDeviceOwnerSingleUserEnabled()) {
// Block this method if the device is in headless main user mode
Preconditions.checkCallAuthorization(
getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
@@ -13438,12 +13431,12 @@
UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
- if (Flags.threadUserRestrictionEnabled()) {
+ if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_THREAD_NETWORK,
new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
}
- if (assistContentUserRestrictionEnabled()) {
+ if (Flags.assistContentUserRestrictionEnabled()) {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ASSIST_CONTENT,
new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
@@ -13777,7 +13770,7 @@
return;
}
- if (!devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
}
@@ -14391,7 +14384,7 @@
public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
throws SecurityException {
Objects.requireNonNull(packages, "packages is null");
- if (!devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingEnabled()) {
for (String pkg : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -15798,7 +15791,7 @@
@Override
public void enforceSecurityLoggingPolicy(boolean enabled) {
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
return;
}
Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
@@ -15808,7 +15801,7 @@
@Override
public void enforceAuditLoggingPolicy(boolean enabled) {
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
return;
}
Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
@@ -16345,7 +16338,7 @@
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
- if (permissionMigrationForZeroTrustImplEnabled()) {
+ if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
final UserHandle user = UserHandle.of(userId);
final String roleHolderPackage = getRoleHolderPackageNameOnUser(
RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId);
@@ -16359,7 +16352,7 @@
@Override
public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin, String callerPackage) {
- if (permissionMigrationForZeroTrustImplEnabled()) {
+ if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
CallerIdentity caller = getCallerIdentity(admin, callerPackage);
enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE,
MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(),
@@ -16816,7 +16809,7 @@
return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
}
- if (headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+ if (Flags.headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
ensureSetUpUser = mUserManagerInternal.getMainUserId();
if (ensureSetUpUser == UserHandle.USER_NULL) {
return STATUS_HEADLESS_ONLY_SYSTEM_USER;
@@ -17723,7 +17716,7 @@
}
final CallerIdentity caller = getCallerIdentity(who, packageName);
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
@@ -17783,7 +17776,7 @@
return mInjector.securityLogGetLoggingEnabledProperty();
}
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
@@ -17881,7 +17874,7 @@
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
@@ -17936,7 +17929,7 @@
}
final CallerIdentity caller = getCallerIdentity(callingPackage);
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
throw new UnsupportedOperationException("Audit log not enabled");
}
@@ -17964,7 +17957,7 @@
return false;
}
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
throw new UnsupportedOperationException("Audit log not enabled");
}
@@ -18230,7 +18223,7 @@
toggleBackupServiceActive(caller.getUserId(), enabled);
- if (backupServiceSecurityLogEventEnabled()) {
+ if (Flags.backupServiceSecurityLogEventEnabled()) {
if (SecurityLog.isLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
@@ -20951,7 +20944,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
- if (permissionMigrationForZeroTrustImplEnabled()) {
+ if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName());
} else {
Preconditions.checkCallAuthorization(
@@ -21555,7 +21548,7 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- int deviceOwnerUserId = headlessDeviceOwnerSingleUserEnabled()
+ int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
&& getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
? mUserManagerInternal.getMainUserId()
: UserHandle.USER_SYSTEM;
@@ -21932,7 +21925,7 @@
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
- if (!policyEngineMigrationV2Enabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()) {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
@@ -21942,7 +21935,7 @@
}
synchronized (getLockObject()) {
- if (policyEngineMigrationV2Enabled()) {
+ if (Flags.policyEngineMigrationV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
caller.getPackageName(),
@@ -21982,7 +21975,7 @@
@Override
public boolean isUsbDataSignalingEnabled(String packageName) {
final CallerIdentity caller = getCallerIdentity(packageName);
- if (policyEngineMigrationV2Enabled()) {
+ if (Flags.policyEngineMigrationV2Enabled()) {
Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.USB_DATA_SIGNALING,
caller.getUserId());
@@ -22107,9 +22100,9 @@
enforcePermission(MANAGE_DEVICE_POLICY_THEFT_DETECTION, caller.getPackageName(),
caller.getUserId());
- //STOPSHIP: replace 1<<9 with
- // LockPatternUtils.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST once ag/26042068 lands
- return 0 != (mLockPatternUtils.getStrongAuthForUser(caller.getUserId()) & (1 << 9));
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ 0 != (mLockPatternUtils.getStrongAuthForUser(caller.getUserId())
+ & SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST));
}
@Override
@@ -24235,7 +24228,7 @@
@Override
public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24247,7 +24240,7 @@
@Override
public int getMaxPolicyStorageLimit(String callerPackageName) {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return -1;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index d9fef10..9d73ed0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -16,11 +16,11 @@
package com.android.server.devicepolicy;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
-import static android.app.admin.flags.Flags.securityLogV2Enabled;
import android.annotation.Nullable;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -400,7 +400,7 @@
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
@@ -463,7 +463,7 @@
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
- mSecurityLoggingMigrated = securityLogV2Enabled()
+ mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
break;
default:
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index e8c5658..8cb511e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -17,11 +17,11 @@
package com.android.server.devicepolicy;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.app.admin.flags.Flags.defaultSmsPersonalAppSuspensionFixEnabled;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -206,7 +206,7 @@
private String getDefaultSmsPackage() {
//TODO(b/319449037): Unflag the following change.
- if (defaultSmsPersonalAppSuspensionFixEnabled()) {
+ if (Flags.defaultSmsPersonalAppSuspensionFixEnabled()) {
return SmsApplication.getDefaultSmsApplicationAsUser(
mContext, /*updateIfNeeded=*/ false, mContext.getUser())
.getPackageName();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index b6ab4c7..c582a46 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -16,8 +16,6 @@
package com.android.server.devicepolicy;
-import static android.app.admin.flags.Flags.securityLogV2Enabled;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -25,6 +23,7 @@
import android.app.admin.IAuditLogEventsCallback;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
@@ -468,11 +467,11 @@
assignLogId(event);
}
- if (!securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
addToLegacyBufferLocked(dedupedLogs);
}
- if (securityLogV2Enabled() && mAuditLogEnabled) {
+ if (Flags.securityLogV2Enabled() && mAuditLogEnabled) {
addAuditLogEventsLocked(dedupedLogs);
}
}
@@ -549,7 +548,7 @@
saveLastEvents(newLogs);
newLogs.clear();
- if (!securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
}
} catch (IOException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ee758db..e19f08c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1486,7 +1486,6 @@
VcnManagementService vcnManagement = null;
NetworkPolicyManagerService networkPolicy = null;
WindowManagerService wm = null;
- SerialService serial = null;
NetworkTimeUpdateService networkTimeUpdater = null;
InputManagerService inputManager = null;
TelephonyRegistry telephonyRegistry = null;
@@ -2362,13 +2361,7 @@
if (!isWatch) {
t.traceBegin("StartSerialService");
- try {
- // Serial port support
- serial = new SerialService(context);
- ServiceManager.addService(Context.SERIAL_SERVICE, serial);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting SerialService", e);
- }
+ mSystemServiceManager.startService(SerialService.Lifecycle.class);
t.traceEnd();
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
index 3458b08..306b4f8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
@@ -85,7 +85,7 @@
@Test
public void testType() {
- assertEquals(BrightnessClamper.Type.BEDTIME_MODE, mClamper.getType());
+ assertEquals(BrightnessClamper.Type.WEAR_BEDTIME_MODE, mClamper.getType());
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index dfb8fda..240ddf5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -751,7 +751,6 @@
app.mState.setCurAdj(setAdj);
app.setLastActivityTime(lastActivityTime);
mPidToRss.put(app.getPid(), lastRss);
- app.mState.setCached(false);
for (int i = 0; i < wentToForegroundCount; ++i) {
app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 47928bc..1226f0c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -470,6 +470,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
+ app.mState.setCurAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -494,6 +495,8 @@
field.set(callback, PROCESS_STATE_TOP);
field = callback.getClass().getDeclaredField("schedGroup");
field.set(callback, SCHED_GROUP_TOP_APP);
+ field = callback.getClass().getDeclaredField("mAdjType");
+ field.set(callback, "vis-activity");
return 0;
})).when(wpc).computeOomAdjFromActivities(
any(WindowProcessController.ComputeOomAdjCallback.class));
@@ -501,6 +504,9 @@
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
+ assertFalse(app.mState.isCached());
+ assertFalse(app.mState.isEmpty());
+ assertEquals("vis-activity", app.mState.getAdjType());
}
@SuppressWarnings("GuardedBy")
@@ -871,8 +877,8 @@
public void testUpdateOomAdj_DoOne_NonCachedToCached() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setCached(false);
app.mState.setCurRawAdj(SERVICE_ADJ);
+ app.mState.setCurAdj(SERVICE_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -2546,7 +2552,6 @@
s.startRequested = true;
s.lastActivity = now;
- app.mState.setCached(false);
app.mServices.startService(s);
app.mState.setHasShownUi(true);
@@ -2559,7 +2564,6 @@
s2.startRequested = true;
s2.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- app2.mState.setCached(false);
app2.mServices.startService(s2);
app2.mState.setHasShownUi(false);
@@ -2577,7 +2581,6 @@
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
- app.mState.setCached(false);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
@@ -2605,7 +2608,6 @@
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
- app.mState.setCached(true);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
@@ -3035,7 +3037,6 @@
state.setHasTopUi(mHasTopUi);
state.setRunningRemoteAnimation(mRunningRemoteAnimation);
state.setHasOverlayUi(mHasOverlayUi);
- state.setCached(mCached);
state.setLastTopTime(mLastTopTime);
state.setForcingToImportant(mForcingToImportant);
services.setConnectionGroup(mConnectionGroup);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 2f12a3b..709a804 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -21,7 +21,6 @@
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_HOME;
-import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
@@ -30,10 +29,10 @@
import static android.os.UserHandle.USER_SYSTEM;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.HOME_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
-import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -47,8 +46,8 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -582,7 +581,6 @@
app.mState.setSetAdj(adj);
app.mState.setCurCapability(cap);
app.mState.setSetCapability(cap);
- app.mState.setCached(procState >= PROCESS_STATE_LAST_ACTIVITY || adj >= CACHED_APP_MIN_ADJ);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index a140730..d6e246f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -23,7 +23,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -33,14 +39,17 @@
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.util.Log;
-import android.util.Xml;
+import android.crashrecovery.flags.Flags;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog;
import com.android.server.SystemConfig;
+import com.android.server.pm.ApexManager;
import org.junit.After;
import org.junit.Before;
@@ -49,18 +58,16 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
-import org.xmlpull.v1.XmlPullParser;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import java.time.Duration;
import java.util.List;
-import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -78,10 +85,18 @@
@Mock
PackageManager mMockPackageManager;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ApexManager mApexManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private MockitoSession mSession;
private static final String APP_A = "com.package.a";
private static final String APP_B = "com.package.b";
+ private static final String APP_C = "com.package.c";
private static final long VERSION_CODE = 1L;
+ private static final long VERSION_CODE_2 = 2L;
private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
private SystemConfig mSysConfig;
@@ -101,7 +116,6 @@
// Mock PackageWatchdog
doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
.when(() -> PackageWatchdog.getInstance(mMockContext));
-
}
@After
@@ -121,7 +135,7 @@
@Test
public void testHealthCheckLevels() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
@@ -165,14 +179,14 @@
@Test
public void testIsPersistent() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
assertTrue(observer.isPersistent());
}
@Test
public void testMayObservePackage_withoutAnyRollback() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
assertFalse(observer.mayObservePackage(APP_A));
@@ -182,7 +196,7 @@
public void testMayObservePackage_forPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM;
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -197,7 +211,7 @@
public void testMayObservePackage_forNonPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
@@ -208,96 +222,720 @@
}
/**
- * Test that isAutomaticRollbackDenied works correctly when packages that are not
- * denied are sent.
+ * Test that when impactLevel is low returns user impact level 70
*/
@Test
- public void isRollbackAllowedTest_false() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+ public void healthCheckFailed_impactLevelLow_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Test that isAutomaticRollbackDenied works correctly when packages that are
- * denied are sent.
+ * HealthCheckFailed should only return low impact rollbacks. High impact rollbacks are only
+ * for bootloop.
*/
@Test
- public void isRollbackAllowedTest_true() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+ public void healthCheckFailed_impactLevelHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Test that isAutomaticRollbackDenied works correctly when no config is present
+ * When the rollback impact level is manual only return user impact level 0. (User impact level
+ * 0 is ignored by package watchdog)
*/
@Test
- public void isRollbackAllowedTest_noConfig() throws IOException {
- final File folder = createTempSubfolder("folder");
+ public void healthCheckFailed_impactLevelManualOnly_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
- *
- * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
- * @param fileName name of the file (e.g. filename.xml) to create
- * @param contents contents to write to the file
- * @return the newly created file
+ * When both low impact and high impact are present, return 70.
*/
- private File createTempFile(File folder, String fileName, String contents)
- throws IOException {
- File file = new File(folder, fileName);
- BufferedWriter bw = new BufferedWriter(new FileWriter(file));
- bw.write(contents);
- bw.close();
+ @Test
+ public void healthCheckFailed_impactLevelLowAndHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
- // Print to logcat for test debugging.
- Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
- Scanner input = new Scanner(file);
- while (input.hasNextLine()) {
- Log.d(LOG_TAG, input.nextLine());
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+ }
+
+ /**
+ * When low impact rollback is available roll it back.
+ */
+ @Test
+ public void execute_impactLevelLow_nativeCrash_rollback()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager).getAvailableRollbacks();
+ verify(mRollbackManager).commitRollback(eq(rollbackId), any(), any());
+ }
+
+ /**
+ * Rollback the failing package if rollback is available for it
+ */
+ @Test
+ public void execute_impactLevelLow_rollbackFailedPackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager).commitRollback(argument.capture(), any(), any());
+ // Rollback package App B as the failing package is B
+ assertThat(argument.getValue()).isEqualTo(rollbackId2);
+ }
+
+ /**
+ * Rollback all available rollbacks if the rollback is not available for failing package.
+ */
+ @Test
+ public void execute_impactLevelLow_rollbackAll()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(2)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
+ }
+
+ /**
+ * rollback low impact package if both low and high impact packages are available
+ */
+ @Test
+ public void execute_impactLevelLowAndHigh_rollbackLow()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Don't roll back high impact package if only high impact package is available. high impact
+ * rollback to be rolled back only on bootloop.
+ */
+ @Test
+ public void execute_impactLevelHigh_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
+
+ }
+
+ /**
+ * Test that when impactLevel is low returns user impact level 70
+ */
+ @Test
+ public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onBootLoop(1));
+ }
+
+ @Test
+ public void onBootLoop_impactLevelHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * When the rollback impact level is manual only return user impact level 0. (User impact level
+ * 0 is ignored by package watchdog)
+ */
+ @Test
+ public void onBootLoop_impactLevelManualOnly_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * When both low impact and high impact are present, return 70.
+ */
+ @Test
+ public void onBootLoop_impactLevelLowAndHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * Rollback all available rollbacks if the rollback is not available for failing package.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelLow_rollbackAll()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(2)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
+ }
+
+ /**
+ * rollback low impact package if both low and high impact packages are available
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Rollback high impact package if only high impact package is available
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback high impact packages when no other rollback available
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
+ }
+
+ /**
+ * Rollback only low impact available rollbacks if both low and manual only are available.
+ */
+ @Test
+ public void execute_impactLevelLowAndManual_rollbackLowImpactOnly()
+ throws PackageManager.NameNotFoundException, InterruptedException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Do not roll back if only manual rollback is available.
+ */
+ @Test
+ public void execute_impactLevelManual_rollbackLowImpactOnly()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
+ }
+
+ /**
+ * Rollback alphabetically first package if multiple high impact rollbacks are available.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ int rollbackId2 = 2;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback APP_A because it is first alphabetically
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
+ }
+
+ private void waitForIdleHandler(Handler handler, Duration timeout) {
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ // Remove idle handler
+ return false;
+ });
+ try {
+ latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupted unexpectedly: " + e);
}
-
- return file;
- }
-
- private void readPermissions(File libraryDir, int permissionFlag) {
- final XmlPullParser parser = Xml.newPullParser();
- mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
- }
-
- /**
- * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
- *
- * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
- * @return the folder
- */
- private File createTempSubfolder(String folderName)
- throws IOException {
- File folder = new File(mTemporaryFolder.getRoot(), folderName);
- folder.mkdirs();
- return folder;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index b224773..f3cd0d6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1360,7 +1360,7 @@
DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ false, DISPLAY_0);
- verify(mMockWindowManager).setForceShowMagnifiableBounds(DISPLAY_0, true);
+ verify(mMockWindowManager).setFullscreenMagnificationActivated(DISPLAY_0, true);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 4307ec5..cea10ea 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -87,6 +87,7 @@
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -1223,6 +1224,7 @@
private final UserManagerInternal mUserManagerInternalMock;
private final WindowManagerService mWindowManagerMock;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private final PowerManagerInternal mPowerManagerInternal;
private final KeyguardManager mKeyguardManagerMock;
private final LockPatternUtils mLockPatternUtilsMock;
@@ -1244,6 +1246,7 @@
mWindowManagerMock = mock(WindowManagerService.class);
mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
mStorageManagerMock = mock(IStorageManager.class);
+ mPowerManagerInternal = mock(PowerManagerInternal.class);
mKeyguardManagerMock = mock(KeyguardManager.class);
when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
mLockPatternUtilsMock = mock(LockPatternUtils.class);
@@ -1309,6 +1312,11 @@
}
@Override
+ PowerManagerInternal getPowerManagerInternal() {
+ return mPowerManagerInternal;
+ }
+
+ @Override
KeyguardManager getKeyguardManager() {
return mKeyguardManagerMock;
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 132b621..ec3e97b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -38,6 +38,7 @@
import android.app.WindowConfiguration;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.VirtualDeviceManager;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -712,6 +713,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -732,6 +734,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -753,6 +756,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -774,6 +778,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ Collections.singleton(blockedComponent),
@@ -795,6 +800,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ false,
/* activityPolicyExemptions= */ Collections.singleton(allowedComponent),
@@ -816,6 +822,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -837,6 +844,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -858,6 +866,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -880,6 +889,7 @@
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 07e6ab2..fd880dd 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
+import android.content.AttributionSource;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerGlobal;
@@ -95,6 +96,7 @@
mInputController = new InputController(mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
InstrumentationRegistry.getTargetContext().getSystemService(WindowManager.class),
+ AttributionSource.myAttributionSource(),
threadVerifier);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index faece4f..67fc564 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -32,6 +32,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.AttributionSource;
import android.hardware.Sensor;
import android.os.Binder;
import android.os.IBinder;
@@ -96,6 +97,7 @@
Throwable thrown = assertThrows(
RuntimeException.class,
() -> new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+ AttributionSource.myAttributionSource(),
mVirtualSensorCallback, List.of(mVirtualSensorConfig)));
assertThat(thrown.getCause().getMessage())
@@ -168,6 +170,7 @@
doReturn(VIRTUAL_DEVICE_ID).when(mVirtualDevice).getDeviceId();
SensorController sensorController = new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+ AttributionSource.myAttributionSource(),
mVirtualSensorCallback, List.of(mVirtualSensorConfig));
List<VirtualSensor> sensors = sensorController.getSensorList();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 157e893..5e0806d 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -389,7 +389,8 @@
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
mInputController = new InputController(mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
- mContext.getSystemService(WindowManager.class), threadVerifier);
+ mContext.getSystemService(WindowManager.class),
+ AttributionSource.myAttributionSource(), threadVerifier);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index ef5270e..52f28b9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -27,6 +27,7 @@
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.media.AudioPlaybackConfiguration;
@@ -72,11 +73,13 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mVirtualAudioController = new VirtualAudioController(mContext);
+ mVirtualAudioController = new VirtualAudioController(mContext,
+ AttributionSource.myAttributionSource());
mGenericWindowPolicyController =
new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 81981e6..9ca1df0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -37,6 +37,7 @@
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.content.AttributionSource;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -104,7 +105,7 @@
public void registerCamera_registersCamera(int lensFacing) throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
- CAMERA_SENSOR_ORIENTATION_1, lensFacing));
+ CAMERA_SENSOR_ORIENTATION_1, lensFacing), AttributionSource.myAttributionSource());
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -121,7 +122,7 @@
VirtualCameraConfig config = createVirtualCameraConfig(
CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1);
- mVirtualCameraController.registerCamera(config);
+ mVirtualCameraController.registerCamera(config, AttributionSource.myAttributionSource());
mVirtualCameraController.unregisterCamera(config);
@@ -131,11 +132,15 @@
@Test
public void close_unregistersAllCameras() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
- CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
+ CAMERA_NAME_1,
+ CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1),
+ AttributionSource.myAttributionSource());
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2, CAMERA_NAME_2,
- CAMERA_SENSOR_ORIENTATION_2, CAMERA_LENS_FACING_2));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
+ CAMERA_NAME_2,
+ CAMERA_SENSOR_ORIENTATION_2, CAMERA_LENS_FACING_2),
+ AttributionSource.myAttributionSource());
mVirtualCameraController.close();
@@ -160,11 +165,12 @@
int lensFacing) {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
- CAMERA_SENSOR_ORIENTATION_1, lensFacing));
+ CAMERA_SENSOR_ORIENTATION_1, lensFacing), AttributionSource.myAttributionSource());
assertThrows(IllegalArgumentException.class,
() -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
- CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2, lensFacing)));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
+ CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2, lensFacing),
+ AttributionSource.myAttributionSource()));
}
@Parameters(method = "getAllLensFacingDirections")
@@ -176,8 +182,9 @@
assertThrows(IllegalArgumentException.class,
() -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
- CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1, lensFacing)));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
+ CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1, lensFacing),
+ AttributionSource.myAttributionSource()));
}
private VirtualCameraConfig createVirtualCameraConfig(
@@ -203,7 +210,7 @@
}
private static Integer[] getAllLensFacingDirections() {
- return new Integer[] {
+ return new Integer[]{
LENS_FACING_BACK,
LENS_FACING_FRONT
};
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index a529382..5c6f3c9 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -205,7 +205,6 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
@@ -2144,14 +2143,12 @@
assertFalse(mService.isUidNetworkingBlocked(UID_E, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainEnabled() throws Exception {
verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnProcStateChange() throws Exception {
@@ -2181,7 +2178,6 @@
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnAllowlistChange() throws Exception {
@@ -2220,7 +2216,6 @@
assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnTempAllowlistChange() throws Exception {
@@ -2250,7 +2245,6 @@
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersProcStateChanges() throws Exception {
@@ -2313,7 +2307,6 @@
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersStaleChanges() throws Exception {
@@ -2334,7 +2327,6 @@
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersCapabilityChanges() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 1ae6e63..4a43c2e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -43,8 +43,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
import android.os.FileUtils;
import android.os.Looper;
import android.os.RemoteException;
@@ -115,9 +117,6 @@
private BackgroundInstallControlCallbackHelper mCallbackHelper;
@Captor
- private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
-
- @Captor
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
@Before
@@ -137,8 +136,8 @@
mUsageEventListener = mUsageEventListenerCaptor.getValue();
mBackgroundInstallControlService.onStart(true);
- verify(mPackageManagerInternal).getPackageList(mPackageListObserverCaptor.capture());
- mPackageListObserver = mPackageListObserverCaptor.getValue();
+
+ mPackageListObserver = mBackgroundInstallControlService.mPackageObserver;
}
@After
@@ -554,6 +553,7 @@
assertEquals(0, foregroundTimeFrames.size());
}
+ //package installed, but no UI interaction found
@Test
public void testHandleUsageEvent_packageAddedNoUsageEvent()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
@@ -571,12 +571,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -590,6 +588,10 @@
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
}
+ private long convertToTestAdjustTimestamp(long timestamp) {
+ return timestamp - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ }
+
@Test
public void testHandleUsageEvent_packageAddedInsideTimeFrame()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
@@ -607,12 +609,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -639,6 +639,122 @@
}
@Test
+ public void testHandleUsageEvent_fallsBackToAppInfoTimeWhenHistoricalSessionsNotFound()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
+
+ FieldSetter.setField(
+ appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ // create timestamp is after generated foreground events (hence not considered
+ // foreground install)
+ convertToTestAdjustTimestamp(USAGE_EVENT_TIMESTAMP_2 + 100000));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ createPackageManagerHistoricalSessions(List.of(), USER_ID_1);
+
+ // The 2 relevants usage events are before the timeframe, the app is not considered
+ // foreground installed.
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyString(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testHandleUsageEvent_usesHistoricalSessionCreateTimeWhenHistoricalSessionsFound()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
+
+ FieldSetter.setField(
+ appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ //create timestamp is out of window of (after) the interact events
+ convertToTestAdjustTimestamp(USAGE_EVENT_TIMESTAMP_2 + 1));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ PackageInstaller.SessionInfo installSession1 = mock(PackageInstaller.SessionInfo.class);
+ PackageInstaller.SessionInfo installSession2 = mock(PackageInstaller.SessionInfo.class);
+ doReturn(convertToTestAdjustTimestamp(0L)).when(installSession1).getCreatedMillis();
+ doReturn(convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1)).when(installSession2)
+ .getCreatedMillis();
+ doReturn(PACKAGE_NAME_1).when(installSession1).getAppPackageName();
+ doReturn(PACKAGE_NAME_1).when(installSession2).getAppPackageName();
+ createPackageManagerHistoricalSessions(List.of(installSession1, installSession2),
+ USER_ID_1);
+
+ // The following 2 generated usage events occur after historical session create times hence,
+ // considered foreground install. The appInfo createTimestamp occurs after events, so the
+ // app would be considered background install if it falls back to it as reference create
+ // timestamp.
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyString(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ }
+
+ private void createPackageManagerHistoricalSessions(
+ List<PackageInstaller.SessionInfo> sessions, int userId) {
+ ParceledListSlice<PackageInstaller.SessionInfo> mockParcelList =
+ mock(ParceledListSlice.class);
+ when(mockParcelList.getList()).thenReturn(sessions);
+ when(mPackageManagerInternal.getHistoricalSessions(userId)).thenReturn(mockParcelList);
+ }
+
+ @Test
public void testHandleUsageEvent_packageAddedOutsideTimeFrame1()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -655,12 +771,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -708,12 +822,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -765,12 +877,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -818,12 +928,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
diff --git a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
index 8bccce1..f5c6795 100644
--- a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
+++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
@@ -16,41 +16,60 @@
package com.android.server.search;
-import android.app.SearchManager;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+
import android.app.SearchableInfo;
import android.app.SearchableInfo.ActionKeyInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.os.RemoteException;
-import com.android.server.search.Searchables;
-import android.test.AndroidTestCase;
+import android.content.pm.PackageManagerInternal;
import android.test.MoreAsserts;
-import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
-import java.util.ArrayList;
-import java.util.List;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
-/**
- * To launch this test from the command line:
- *
- * adb shell am instrument -w \
- * -e class com.android.unit_tests.SearchablesTest \
- * com.android.unit_tests/android.test.InstrumentationTestRunner
- */
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class SearchablesTest extends AndroidTestCase {
-
+public class SearchablesTest {
+ @Mock protected PackageManagerInternal mPackageManagerInternal;
+
+ private Context mContext;
+
+ @Before
+ public final void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ @After
+ public final void tearDown() {
+ Mockito.framework().clearInlineMocks();
+ }
+
/*
* SearchableInfo tests
* Mock the context so I can provide very specific input data
@@ -69,15 +88,15 @@
* Test that non-searchable activities return no searchable info (this would typically
* trigger the use of the default searchable e.g. contacts)
*/
+ @Test
public void testNonSearchable() {
// test basic array & hashmap
Searchables searchables = new Searchables(mContext, 0);
searchables.updateSearchableList();
// confirm that we return null for non-searchy activities
- ComponentName nonActivity = new ComponentName(
- "com.android.frameworks.coretests",
- "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY");
+ ComponentName nonActivity = new ComponentName("com.android.frameworks.servicestests",
+ "com.android.frameworks.servicestests.activity.NO_SEARCH_ACTIVITY");
SearchableInfo si = searchables.getSearchableInfo(nonActivity);
assertNull(si);
}
@@ -97,13 +116,11 @@
* getIcon works
*/
+ @Test
public void testSearchablesListReal() {
- MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
- MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+ doReturn(true).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
- // build item list with real-world source data
- mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
- Searchables searchables = new Searchables(mockContext, 0);
+ Searchables searchables = new Searchables(mContext, 0);
searchables.updateSearchableList();
// tests with "real" searchables (deprecate, this should be a unit test)
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
@@ -117,12 +134,11 @@
/**
* This round of tests confirms good operations with "zero" searchables found
*/
+ @Test
public void testSearchablesListEmpty() {
- MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
- MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+ doReturn(false).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
- mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
- Searchables searchables = new Searchables(mockContext, 0);
+ Searchables searchables = new Searchables(mContext, 0);
searchables.updateSearchableList();
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
assertNotNull(searchablesList);
@@ -219,231 +235,6 @@
if (s != null) {
MoreAsserts.assertNotEqual(s, "");
}
- }
-
- /**
- * This is a mock for context. Used to perform a true unit test on SearchableInfo.
- *
- */
- private class MyMockContext extends MockContext {
-
- protected Context mRealContext;
- protected PackageManager mPackageManager;
-
- /**
- * Constructor.
- *
- * @param realContext Please pass in a real context for some pass-throughs to function.
- */
- MyMockContext(Context realContext, PackageManager packageManager) {
- mRealContext = realContext;
- mPackageManager = packageManager;
- }
-
- /**
- * Resources. Pass through for now.
- */
- @Override
- public Resources getResources() {
- return mRealContext.getResources();
- }
-
- /**
- * Package manager. Pass through for now.
- */
- @Override
- public PackageManager getPackageManager() {
- return mPackageManager;
- }
-
- /**
- * Package manager. Pass through for now.
- */
- @Override
- public Context createPackageContext(String packageName, int flags)
- throws PackageManager.NameNotFoundException {
- return mRealContext.createPackageContext(packageName, flags);
- }
-
- /**
- * Message broadcast. Pass through for now.
- */
- @Override
- public void sendBroadcast(Intent intent) {
- mRealContext.sendBroadcast(intent);
- }
- }
-
-/**
- * This is a mock for package manager. Used to perform a true unit test on SearchableInfo.
- *
- */
- private class MyMockPackageManager extends MockPackageManager {
-
- public final static int SEARCHABLES_PASSTHROUGH = 0;
- public final static int SEARCHABLES_MOCK_ZERO = 1;
- public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
- public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
-
- protected PackageManager mRealPackageManager;
- protected int mSearchablesMode;
-
- public MyMockPackageManager(PackageManager realPM) {
- mRealPackageManager = realPM;
- mSearchablesMode = SEARCHABLES_PASSTHROUGH;
- }
-
- /**
- * Set the mode for various tests.
- */
- public void setSearchablesMode(int newMode) {
- switch (newMode) {
- case SEARCHABLES_PASSTHROUGH:
- case SEARCHABLES_MOCK_ZERO:
- mSearchablesMode = newMode;
- break;
-
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Find activities that support a given intent.
- *
- * Retrieve all activities that can be performed for the given intent.
- *
- * @param intent The desired intent as per resolveActivity().
- * @param flags Additional option flags. The most important is
- * MATCH_DEFAULT_ONLY, to limit the resolution to only
- * those activities that support the CATEGORY_DEFAULT.
- *
- * @return A List<ResolveInfo> containing one entry for each matching
- * Activity. These are ordered from best to worst match -- that
- * is, the first item in the list is what is returned by
- * resolveActivity(). If there are no matching activities, an empty
- * list is returned.
- */
- @Override
- public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
- assertNotNull(intent);
- assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
- || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
- || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.queryIntentActivities(intent, flags);
- case SEARCHABLES_MOCK_ZERO:
- return null;
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public ResolveInfo resolveActivity(Intent intent, int flags) {
- assertNotNull(intent);
- assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
- || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.resolveActivity(intent, flags);
- case SEARCHABLES_MOCK_ZERO:
- return null;
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Retrieve an XML file from a package. This is a low-level API used to
- * retrieve XML meta data.
- *
- * @param packageName The name of the package that this xml is coming from.
- * Can not be null.
- * @param resid The resource identifier of the desired xml. Can not be 0.
- * @param appInfo Overall information about <var>packageName</var>. This
- * may be null, in which case the application information will be retrieved
- * for you if needed; if you already have this information around, it can
- * be much more efficient to supply it here.
- *
- * @return Returns an TypedXmlPullParser allowing you to parse out the XML
- * data. Returns null if the xml resource could not be found for any
- * reason.
- */
- @Override
- public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
- assertNotNull(packageName);
- MoreAsserts.assertNotEqual(packageName, "");
- MoreAsserts.assertNotEqual(resid, 0);
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.getXml(packageName, resid, appInfo);
- case SEARCHABLES_MOCK_ZERO:
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Find a single content provider by its base path name.
- *
- * @param name The name of the provider to find.
- * @param flags Additional option flags. Currently should always be 0.
- *
- * @return ContentProviderInfo Information about the provider, if found,
- * else null.
- */
- @Override
- public ProviderInfo resolveContentProvider(String name, int flags) {
- assertNotNull(name);
- MoreAsserts.assertNotEqual(name, "");
- assertEquals(flags, 0);
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.resolveContentProvider(name, flags);
- case SEARCHABLES_MOCK_ZERO:
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Get the activity information for a particular activity.
- *
- * @param name The name of the activity to find.
- * @param flags Additional option flags.
- *
- * @return ActivityInfo Information about the activity, if found, else null.
- */
- @Override
- public ActivityInfo getActivityInfo(ComponentName name, int flags)
- throws NameNotFoundException {
- assertNotNull(name);
- MoreAsserts.assertNotEqual(name, "");
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.getActivityInfo(name, flags);
- case SEARCHABLES_MOCK_ZERO:
- throw new NameNotFoundException();
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public int checkPermission(String permName, String pkgName) {
- assertNotNull(permName);
- assertNotNull(pkgName);
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.checkPermission(permName, pkgName);
- case SEARCHABLES_MOCK_ZERO:
- return PackageManager.PERMISSION_DENIED;
- default:
- throw new UnsupportedOperationException();
- }
- }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index eddff9ab..dfda1fc 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -602,56 +602,6 @@
}
/**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app}.
- */
- @Test
- public void automaticRollbackDeny_vending() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages())
- .containsExactly("com.android.vending");
- }
-
- /**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app} without any packages.
- */
- @Test
- public void automaticRollbackDeny_empty() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
- }
-
- /**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app} without the corresponding config.
- */
- @Test
- public void automaticRollbackDeny_noConfig() throws IOException {
- final File folder = createTempSubfolder("folder");
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
- }
-
- /**
* Tests that readPermissions works correctly for the tag: {@code update-ownership}.
*/
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6e5c180..3f2ccaf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -5384,6 +5384,7 @@
ZenRule rule = new ZenRule();
rule.pkg = pkg;
rule.creationTime = createdAt.toEpochMilli();
+ rule.enabled = true;
rule.deletionInstant = deletedAt;
// Plus stuff so that isValidAutomaticRule() passes
rule.name = "A rule from " + pkg + " created on " + createdAt;
@@ -5392,6 +5393,47 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void getAutomaticZenRuleState_ownedRule_returnsRuleState() {
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+ .setConfigurationActivity(
+ new ComponentName(mContext.getPackageName(), "Blah"))
+ .build(),
+ UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
+
+ // Null condition -> STATE_FALSE
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+
+ mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_TRUE);
+
+ mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+
+ mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_UNKNOWN);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void getAutomaticZenRuleState_notOwnedRule_returnsStateUnknown() {
+ // Assume existence of a system-owned rule that is currently ACTIVE.
+ ZenRule systemRule = newZenRule("android", Instant.now(), null);
+ systemRule.zenMode = ZEN_MODE_ALARMS;
+ systemRule.condition = new Condition(systemRule.conditionId, "on", Condition.STATE_TRUE);
+ ZenModeConfig config = mZenModeHelper.mConfig.copy();
+ config.automaticRules.put("systemRule", systemRule);
+ mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.getAutomaticZenRuleState("systemRule")).isEqualTo(
+ Condition.STATE_UNKNOWN);
+ }
+
+ @Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testCallbacks_policy() throws Exception {
setupZenConfig();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index f080341..f54c7e5 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -100,6 +100,8 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ private static final int OLD_USER_ID = 123;
+ private static final int NEW_USER_ID = 456;
private static final int UID = 1;
private static final int VIRTUAL_DEVICE_ID = 1;
private static final String SYSUI_PACKAGE_NAME = "sysui";
@@ -211,10 +213,10 @@
mVibrationSettings.addListener(mListenerMock);
// Testing the broadcast flow manually.
- mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
- new Intent(Intent.ACTION_USER_SWITCHED));
+ mVibrationSettings.mUserSwitchObserver.onUserSwitching(NEW_USER_ID);
+ mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID);
- verify(mListenerMock).onChange();
+ verify(mListenerMock, times(2)).onChange();
}
@Test
@@ -265,8 +267,7 @@
// Trigger multiple observers manually.
mVibrationSettings.mSettingObserver.onChange(false);
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
- new Intent(Intent.ACTION_USER_SWITCHED));
+ mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID);
mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
@@ -834,13 +835,17 @@
assertEquals(VIBRATION_INTENSITY_HIGH,
mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
- // Switching user is not working with FakeSettingsProvider.
- // Testing the broadcast flow manually.
- Settings.System.putIntForUser(mContextSpy.getContentResolver(),
- Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
+ // Test early update of settings based on new user id.
+ putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
+ NEW_USER_ID);
+ mVibrationSettings.mUserSwitchObserver.onUserSwitching(NEW_USER_ID);
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+
+ // Test later update of settings for UserHandle.USER_CURRENT.
+ putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
UserHandle.USER_CURRENT);
- mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
- new Intent(Intent.ACTION_USER_SWITCHED));
+ mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID);
assertEquals(VIBRATION_INTENSITY_LOW,
mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
}
@@ -956,12 +961,16 @@
}
private void setUserSetting(String settingName, int value) {
- Settings.System.putIntForUser(
- mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+ putUserSetting(settingName, value, UserHandle.USER_CURRENT);
// FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
mVibrationSettings.mSettingObserver.onChange(false);
}
+ private void putUserSetting(String settingName, int value, int userHandle) {
+ Settings.System.putIntForUser(
+ mContextSpy.getContentResolver(), settingName, value, userHandle);
+ }
+
private void setRingerMode(int ringerMode) {
when(mAudioManagerMock.getRingerModeInternal()).thenReturn(ringerMode);
// Mock AudioManager broadcast of internal ringer mode change.
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 6853c4c..e904eae 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -223,7 +223,11 @@
KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
{"Meta + S -> Launch Default Messaging App",
new int[]{META_KEY, KeyEvent.KEYCODE_S},
- KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON}};
+ KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON},
+ {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyboardLogEvent.DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN,
+ META_ON | CTRL_ON}};
}
@Keep
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 0b466b2..6b614fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -243,7 +243,7 @@
activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID,
new Intent(), null /* intentGrants */, "other.package2",
- /* isShareIdentityEnabled */ false);
+ /* isShareIdentityEnabled */ false, /* userId */ -1, /* recipientAppId */ -1);
verify(activity).getFilteredReferrer(eq("other.package2"));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index c99fda9..ef131ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +29,7 @@
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManagerInternal;
@@ -51,7 +56,10 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
/**
* Tests for the {@link ActivityStarter} class.
@@ -73,9 +81,12 @@
private static final String REGULAR_PACKAGE_1 = "package.app1";
private static final String REGULAR_PACKAGE_2 = "package.app2";
- public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+ private static final Intent TEST_INTENT = new Intent()
+ .setComponent(new ComponentName("package.app3", "someClass"));
- BackgroundActivityStartController mController;
+ public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.LENIENT);
+
+ TestableBackgroundActivityStartController mController;
@Mock
ActivityMetricsLogger mActivityMetricsLogger;
@Mock
@@ -112,6 +123,56 @@
List<String> mShownToasts = new ArrayList<>();
List<BalAllowedLog> mBalAllowedLogs = new ArrayList<>();
+ class TestableBackgroundActivityStartController extends BackgroundActivityStartController {
+ Optional<BalVerdict> mCallerVerdict = Optional.empty();
+ Optional<BalVerdict> mRealCallerVerdict = Optional.empty();
+ Map<WindowProcessController, BalVerdict> mProcessVerdicts = new HashMap<>();
+
+ TestableBackgroundActivityStartController(ActivityTaskManagerService service,
+ ActivityTaskSupervisor supervisor) {
+ super(service, supervisor);
+ }
+
+ @Override
+ protected void showToast(String toastText) {
+ mShownToasts.add(toastText);
+ }
+
+ @Override
+ protected void writeBalAllowedLog(String activityName, int code,
+ BackgroundActivityStartController.BalState state) {
+ mBalAllowedLogs.add(new BalAllowedLog(activityName, code));
+ }
+
+ @Override
+ BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
+ return mCallerVerdict.orElseGet(
+ () -> super.checkBackgroundActivityStartAllowedByCaller(state));
+ }
+
+ public void setCallerVerdict(BalVerdict verdict) {
+ this.mCallerVerdict = Optional.of(verdict);
+ }
+
+ @Override
+ BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
+ return mRealCallerVerdict.orElseGet(
+ () -> super.checkBackgroundActivityStartAllowedBySender(state));
+ }
+
+ public void setRealCallerVerdict(BalVerdict verdict) {
+ this.mRealCallerVerdict = Optional.of(verdict);
+ }
+
+ @Override
+ BalVerdict checkProcessAllowsBal(WindowProcessController app, BalState state) {
+ if (mProcessVerdicts.containsKey(app)) {
+ return mProcessVerdicts.get(app);
+ }
+ return super.checkProcessAllowsBal(app, state);
+ }
+ }
+
@Before
public void setUp() throws Exception {
// wire objects
@@ -127,18 +188,10 @@
//Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController);
setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
- mController = new BackgroundActivityStartController(mService, mSupervisor) {
- @Override
- protected void showToast(String toastText) {
- mShownToasts.add(toastText);
- }
+ mController = new TestableBackgroundActivityStartController(mService, mSupervisor);
- @Override
- protected void writeBalAllowedLog(String activityName, int code,
- BackgroundActivityStartController.BalState state) {
- mBalAllowedLogs.add(new BalAllowedLog(activityName, code));
- }
- };
+ // nicer toString
+ Mockito.when(mPendingIntentRecord.toString()).thenReturn("PendingIntentRecord");
// safe defaults
Mockito.when(mAppOpsManager.checkOpNoThrow(
@@ -146,7 +199,6 @@
anyInt(), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT);
Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
BalVerdict.BLOCK);
-
}
private void setViaReflection(Object o, String property, Object value) {
@@ -175,7 +227,7 @@
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
- Intent intent = new Intent();
+ Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
@@ -186,17 +238,16 @@
// assertions
assertThat(verdict.getCode()).isEqualTo(BackgroundActivityStartController.BAL_BLOCK);
-
- assertThat(mBalAllowedLogs).isEmpty();
+ assertThat(mBalAllowedLogs).isEmpty(); // not allowed
}
+ // Tests for BackgroundActivityStartController.checkBackgroundActivityStart
+
@Test
- public void testRegularActivityStart_allowedBLPC_isAllowed() {
+ public void testRegularActivityStart_notAllowed_isBlocked() {
// setup state
- BalVerdict blpcVerdict = new BalVerdict(
- BackgroundActivityStartController.BAL_ALLOW_PERMISSION, true, "Allowed by BLPC");
- Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
- blpcVerdict);
+ mController.setCallerVerdict(BalVerdict.BLOCK);
+ mController.setRealCallerVerdict(BalVerdict.BLOCK);
// prepare call
int callingUid = REGULAR_UID_1;
@@ -206,7 +257,7 @@
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
- Intent intent = new Intent();
+ Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
@@ -216,28 +267,27 @@
checkedOptions);
// assertions
- assertThat(verdict).isEqualTo(blpcVerdict);
- assertThat(mBalAllowedLogs).containsExactly(
- new BalAllowedLog("", BackgroundActivityStartController.BAL_ALLOW_PERMISSION));
+ assertThat(verdict).isEqualTo(BalVerdict.BLOCK);
+ assertThat(mBalAllowedLogs).isEmpty(); // not allowed
}
@Test
- public void testRegularActivityStart_allowedByCallerBLPC_isAllowed() {
+ public void testRegularActivityStart_allowedByCaller_isAllowed() {
// setup state
- BalVerdict blpcVerdict = new BalVerdict(
- BackgroundActivityStartController.BAL_ALLOW_PERMISSION, true, "Allowed by BLPC");
- Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
- blpcVerdict);
+ BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ "CallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(BalVerdict.BLOCK);
// prepare call
int callingUid = REGULAR_UID_1;
int callingPid = REGULAR_PID_1;
final String callingPackage = REGULAR_PACKAGE_1;
- int realCallingUid = REGULAR_UID_2;
- int realCallingPid = REGULAR_PID_2;
- PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
- Intent intent = new Intent();
+ Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
@@ -247,8 +297,312 @@
checkedOptions);
// assertions
- assertThat(verdict).isEqualTo(blpcVerdict);
+ assertThat(verdict).isEqualTo(callerVerdict);
+ assertThat(mBalAllowedLogs).isEmpty(); // non-critical exception
+ }
+
+ @Test
+ public void testRegularActivityStart_allowedByRealCaller_isAllowed() {
+ // setup state
+ BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ "RealCallerIsVisible");
+ mController.setCallerVerdict(BalVerdict.BLOCK);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(realCallerVerdict);
assertThat(mBalAllowedLogs).containsExactly(
- new BalAllowedLog("", BackgroundActivityStartController.BAL_ALLOW_PERMISSION));
+ new BalAllowedLog("package.app3/someClass", realCallerVerdict.getCode()));
+ // TODO questionable log (should we only log PIs?)
+ }
+
+ @Test
+ public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() {
+ // setup state
+ BalVerdict callerVerdict =
+ new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission");
+ BalVerdict realCallerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(callerVerdict);
+ assertThat(mBalAllowedLogs).containsExactly(new BalAllowedLog("", callerVerdict.getCode()));
+ }
+
+ @Test
+ public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() {
+ // setup state
+ BalVerdict callerVerdict =
+ new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission");
+ BalVerdict realCallerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED)
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(BalVerdict.BLOCK);
+ assertThat(mBalAllowedLogs).isEmpty();
+ }
+
+ @Test
+ public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() {
+ // setup state
+ BalVerdict callerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(BalVerdict.BLOCK);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(callerVerdict);
+ assertThat(mBalAllowedLogs).isEmpty();
+ }
+
+ @Test
+ public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() {
+ // setup state
+ BalVerdict realCallerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ mController.setCallerVerdict(BalVerdict.BLOCK);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(realCallerVerdict);
+ assertThat(mBalAllowedLogs).containsExactly(
+ new BalAllowedLog("package.app3/someClass", BAL_ALLOW_PENDING_INTENT));
+
+ }
+
+ // Tests for BackgroundActivityStartController.checkBackgroundActivityStartAllowedByCaller
+
+ // Tests for BackgroundActivityStartController.checkBackgroundActivityStartAllowedBySender
+
+ // Tests for BalState
+
+ @Test
+ public void testBalState_regularStart_isAutoOptIn() {
+ // setup state
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+ WindowProcessController callerApp = mCallerApp;
+ ActivityRecord resultRecord = null;
+
+ // call
+ BackgroundActivityStartController.BalState balState = mController
+ .new BalState(callingUid, callingPid, callingPackage, realCallingUid,
+ realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
+ resultRecord, intent, checkedOptions);
+
+ // assertions
+ assertThat(balState.mAutoOptInReason).isEqualTo("notPendingIntent");
+ assertThat(balState.mBalAllowedByPiCreator).isEqualTo(BackgroundStartPrivileges.ALLOW_BAL);
+ assertThat(balState.mBalAllowedByPiSender).isEqualTo(BackgroundStartPrivileges.ALLOW_BAL);
+ assertThat(balState.callerExplicitOptInOrAutoOptIn()).isTrue();
+ assertThat(balState.callerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
+ assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.toString()).isEqualTo(
+ "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
+ + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
+ + "false; callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
+ + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
+ + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
+ + ".ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
+ + "isCallForResult: false; isPendingIntent: false; autoOptInReason: "
+ + "notPendingIntent; realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
+ + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
+ + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: null; realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: null]");
+ }
+
+ @Test
+ public void testBalState_pendingIntentForResult_isOptedInForSenderOnly() {
+ // setup state
+ Mockito.when(mPendingIntentRecord.toString()).thenReturn("PendingIntentRecord");
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+ WindowProcessController callerApp = mCallerApp;
+ ActivityRecord resultRecord = mResultRecord;
+
+ // call
+ BackgroundActivityStartController.BalState balState = mController
+ .new BalState(callingUid, callingPid, callingPackage, realCallingUid,
+ realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
+ resultRecord, intent, checkedOptions);
+
+ // assertions
+ assertThat(balState.mAutoOptInReason).isEqualTo("callForResult");
+ assertThat(balState.mBalAllowedByPiCreator).isEqualTo(BackgroundStartPrivileges.NONE);
+ assertThat(balState.mBalAllowedByPiSender).isEqualTo(BackgroundStartPrivileges.ALLOW_BAL);
+ assertThat(balState.callerExplicitOptInOrAutoOptIn()).isFalse();
+ assertThat(balState.callerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
+ assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
+ }
+
+ @Test
+ public void testBalState_pendingIntentWithDefaults_isOptedOut() {
+ // setup state
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+ WindowProcessController callerApp = mCallerApp;
+ ActivityRecord resultRecord = null;
+
+ // call
+ BackgroundActivityStartController.BalState balState = mController
+ .new BalState(callingUid, callingPid, callingPackage, realCallingUid,
+ realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
+ resultRecord, intent, checkedOptions);
+
+ // assertions
+ assertThat(balState.mAutoOptInReason).isNull();
+ assertThat(balState.mBalAllowedByPiCreator).isEqualTo(BackgroundStartPrivileges.NONE);
+ assertThat(balState.mBalAllowedByPiSender).isEqualTo(BackgroundStartPrivileges.ALLOW_FGS);
+ assertThat(balState.isPendingIntent()).isTrue();
+ assertThat(balState.callerExplicitOptInOrAutoOptIn()).isFalse();
+ assertThat(balState.callerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.toString()).isEqualTo(
+ "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
+ + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
+ + "false; callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
+ + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
+ + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
+ + ".NONE; balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
+ + "isCallForResult: false; isPendingIntent: true; autoOptInReason: "
+ + "null; realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
+ + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
+ + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: PendingIntentRecord; realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_FGS; resultIfPiSenderAllowsBal: null]");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 5d14334..5965fae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -39,6 +39,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static org.junit.Assert.assertEquals;
@@ -388,6 +389,24 @@
// The current insets are restored from cache directly.
assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
info.logicalWidth, info.logicalHeight).mConfigFrame);
+
+ // If screen is not fully turned on, then the cache should be preserved.
+ displayPolicy.screenTurnedOff();
+ final TransitionController transitionController = mDisplayContent.mTransitionController;
+ spyOn(transitionController);
+ doReturn(true).when(transitionController).isCollecting();
+ doReturn(Integer.MAX_VALUE).when(transitionController).getCollectingTransitionId();
+ // Make CachedDecorInsets.canPreserve return false.
+ displayPolicy.physicalDisplayUpdated();
+ assertFalse(displayPolicy.shouldKeepCurrentDecorInsets());
+ displayPolicy.getDecorInsetsInfo(info.rotation, info.logicalWidth, info.logicalHeight)
+ .mConfigFrame.offset(1, 1);
+ // Even if CachedDecorInsets.canPreserve returns false, the cache won't be cleared.
+ displayPolicy.updateDecorInsetsInfo();
+ // Successful to restore from cache.
+ displayPolicy.updateCachedDecorInsets();
+ assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
+ info.logicalWidth, info.logicalHeight).mConfigFrame);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 9e2b1ec..266a0d5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -177,8 +177,8 @@
TEST_PID, TEST_UID);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class),
- any(InputChannel.class), any(boolean.class))).thenReturn(true);
+ when(mWm.mInputManager.startDragAndDrop(any(InputChannel.class),
+ any(InputChannel.class))).thenReturn(true);
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
}
@@ -641,8 +641,8 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
- new InputChannel(), true /* isDragDrop */));
+ assertTrue(mWm.mInputManager.startDragAndDrop(new InputChannel(),
+ new InputChannel()));
mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
index 08e6396..402b704 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
@@ -16,33 +16,23 @@
package com.android.server.wm;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-
+import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
+import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.window.TransitionRequestInfo.DisplayChange;
-
-import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
-import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
import androidx.test.filters.SmallTest;
@@ -50,7 +40,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,20 +51,19 @@
*/
@SmallTest
@Presubmit
-public class PhysicalDisplaySwitchTransitionLauncherTest {
+@RunWith(WindowTestRunner.class)
+public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
@Mock
- DisplayContent mDisplayContent;
- @Mock
Context mContext;
@Mock
Resources mResources;
@Mock
- ActivityTaskManagerService mActivityTaskManagerService;
- @Mock
BLASTSyncEngine mSyncEngine;
- @Mock
+
+ WindowTestsBase.TestTransitionPlayer mPlayer;
TransitionController mTransitionController;
+ DisplayContent mDisplayContent;
private PhysicalDisplaySwitchTransitionLauncher mTarget;
private float mOriginalAnimationScale;
@@ -83,9 +71,14 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mTransitionController = new WindowTestsBase.TestTransitionController(mAtm);
+ mTransitionController.setSyncEngine(mSyncEngine);
+ mPlayer = new WindowTestsBase.TestTransitionPlayer(
+ mTransitionController, mAtm.mWindowOrganizerController);
when(mContext.getResources()).thenReturn(mResources);
- mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent,
- mActivityTaskManagerService, mContext, mTransitionController);
+ mDisplayContent = new TestDisplayContent.Builder(mAtm, 100, 150).build();
+ mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent, mAtm, mContext,
+ mTransitionController);
mOriginalAnimationScale = ValueAnimator.getDurationScale();
}
@@ -100,24 +93,23 @@
mTarget.foldStateChanged(FOLDED);
mTarget.foldStateChanged(OPEN);
+ final Rect origBounds = new Rect();
+ mDisplayContent.getBounds(origBounds);
+ origBounds.offsetTo(0, 0);
mTarget.requestDisplaySwitchTransitionIfNeeded(
- /* displayId= */ 123,
- /* oldDisplayWidth= */ 100,
- /* oldDisplayHeight= */ 150,
+ mDisplayContent.getDisplayId(),
+ origBounds.width(),
+ origBounds.height(),
/* newDisplayWidth= */ 200,
/* newDisplayHeight= */ 250
);
- ArgumentCaptor<DisplayChange> displayChangeArgumentCaptor =
- ArgumentCaptor.forClass(DisplayChange.class);
- verify(mTransitionController).requestTransitionIfNeeded(eq(TRANSIT_CHANGE), /* flags= */
- eq(0), eq(mDisplayContent), eq(mDisplayContent), /* remoteTransition= */ isNull(),
- displayChangeArgumentCaptor.capture());
- assertThat(displayChangeArgumentCaptor.getValue().getDisplayId()).isEqualTo(123);
- assertThat(displayChangeArgumentCaptor.getValue().getStartAbsBounds()).isEqualTo(
- new Rect(0, 0, 100, 150));
- assertThat(displayChangeArgumentCaptor.getValue().getEndAbsBounds()).isEqualTo(
- new Rect(0, 0, 200, 250));
+ assertNotNull(mPlayer.mLastRequest);
+ assertEquals(mDisplayContent.getDisplayId(),
+ mPlayer.mLastRequest.getDisplayChange().getDisplayId());
+ assertEquals(origBounds, mPlayer.mLastRequest.getDisplayChange().getStartAbsBounds());
+ assertEquals(new Rect(0, 0, 200, 250),
+ mPlayer.mLastRequest.getDisplayChange().getEndAbsBounds());
}
@Test
@@ -148,7 +140,7 @@
mTarget.foldStateChanged(FOLDED);
mTarget.foldStateChanged(OPEN);
requestDisplaySwitch();
- clearInvocations(mTransitionController);
+ mPlayer.mLastRequest = null;
requestDisplaySwitch();
@@ -220,7 +212,6 @@
@Test
public void testDisplaySwitchAfterUnfolding_otherCollectingTransition_collectsDisplaySwitch() {
- givenCollectingTransition(createTransition(TRANSIT_CHANGE));
givenAllAnimationsEnabled();
mTarget.foldStateChanged(FOLDED);
@@ -228,7 +219,8 @@
requestDisplaySwitch();
// Collects to the current transition
- verify(mTransitionController).collect(mDisplayContent);
+ assertTrue(mTransitionController.getCollectingTransition().mParticipants.contains(
+ mDisplayContent));
}
@@ -245,20 +237,18 @@
}
private void assertTransitionRequested() {
- verify(mTransitionController).requestTransitionIfNeeded(anyInt(), anyInt(), any(), any(),
- any(), any());
+ assertNotNull(mPlayer.mLastRequest);
}
private void assertTransitionNotRequested() {
- verify(mTransitionController, never()).requestTransitionIfNeeded(anyInt(), anyInt(), any(),
- any(), any(), any());
+ assertNull(mPlayer.mLastRequest);
}
private void requestDisplaySwitch() {
mTarget.requestDisplaySwitchTransitionIfNeeded(
- /* displayId= */ 123,
- /* oldDisplayWidth= */ 100,
- /* oldDisplayHeight= */ 150,
+ mDisplayContent.getDisplayId(),
+ mDisplayContent.getBounds().width(),
+ mDisplayContent.getBounds().height(),
/* newDisplayWidth= */ 200,
/* newDisplayHeight= */ 250
);
@@ -280,16 +270,11 @@
}
private void givenShellTransitionsEnabled(boolean enabled) {
- when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
- }
-
- private void givenCollectingTransition(@Nullable Transition transition) {
- when(mTransitionController.isCollecting()).thenReturn(transition != null);
- when(mTransitionController.getCollectingTransition()).thenReturn(transition);
- }
-
- private Transition createTransition(int type) {
- return new Transition(type, /* flags= */ 0, mTransitionController, mSyncEngine);
+ if (enabled) {
+ mTransitionController.registerTransitionPlayer(mPlayer, null /* proc */);
+ } else {
+ mTransitionController.detachPlayer();
+ }
}
private void givenDisplayContentHasContent(boolean hasContent) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index f42cdb8..b96f39d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -46,6 +47,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -100,6 +102,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Binder;
import android.os.RemoteException;
@@ -153,6 +156,8 @@
private static final String CONFIG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES =
"never_constrain_display_apis_all_packages";
+ private static final float DELTA_ASPECT_RATIO_TOLERANCE = 0.005f;
+
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -211,90 +216,46 @@
@Test
public void testHorizontalReachabilityEnabledForTranslucentActivities() {
- setUpDisplaySizeWithApp(2500, 1000);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- final LetterboxConfiguration config = mWm.mLetterboxConfiguration;
- config.setTranslucentLetterboxingOverrideEnabled(true);
- config.setLetterboxHorizontalPositionMultiplier(0.5f);
- config.setIsHorizontalReachabilityEnabled(true);
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 2500, /* dh */1000,
+ SCREEN_ORIENTATION_PORTRAIT, /* minAspectRatio */ 0f,
+ /* horizontalReachability */ true);
+ }
- // Opaque activity
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- addWindowToActivity(mActivity);
- mActivity.mRootWindowContainer.performSurfacePlacement();
-
- // Translucent Activity
- final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
- .setActivityTheme(android.R.style.Theme_Translucent)
- .setLaunchedFromUid(mActivity.getUid())
- .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
- .build();
- mTask.addChild(translucentActivity);
-
- spyOn(translucentActivity.mLetterboxUiController);
- doReturn(true).when(translucentActivity.mLetterboxUiController)
- .shouldShowLetterboxUi(any());
-
- addWindowToActivity(translucentActivity);
- translucentActivity.mRootWindowContainer.performSurfacePlacement();
-
- final Function<ActivityRecord, Rect> innerBoundsOf =
- (ActivityRecord a) -> {
- final Rect bounds = new Rect();
- a.mLetterboxUiController.getLetterboxInnerBounds(bounds);
- return bounds;
- };
- final Runnable checkLetterboxPositions = () -> assertEquals(innerBoundsOf.apply(mActivity),
- innerBoundsOf.apply(translucentActivity));
- final Runnable checkIsLeft = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).left).isEqualTo(0);
- final Runnable checkIsRight = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).right).isEqualTo(2500);
- final Runnable checkIsCentered = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).left > 0
- && innerBoundsOf.apply(translucentActivity).right < 2500).isTrue();
-
- final Consumer<Integer> doubleClick =
- (Integer x) -> {
- mActivity.mLetterboxUiController.handleHorizontalDoubleTap(x);
- mActivity.mRootWindowContainer.performSurfacePlacement();
- };
-
- // Initial state
- checkIsCentered.run();
-
- // Double-click left
- doubleClick.accept(/* x */ 10);
- checkLetterboxPositions.run();
- checkIsLeft.run();
-
- // Double-click right
- doubleClick.accept(/* x */ 1990);
- checkLetterboxPositions.run();
- checkIsCentered.run();
-
- // Double-click right
- doubleClick.accept(/* x */ 1990);
- checkLetterboxPositions.run();
- checkIsRight.run();
-
- // Double-click left
- doubleClick.accept(/* x */ 10);
- checkLetterboxPositions.run();
- checkIsCentered.run();
+ @Test
+ public void testHorizontalReachabilityEnabled_TranslucentPortraitActivities_portraitDisplay() {
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 1400, /* dh */1600,
+ SCREEN_ORIENTATION_PORTRAIT, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ /* horizontalReachability */ true);
}
@Test
public void testVerticalReachabilityEnabledForTranslucentActivities() {
- setUpDisplaySizeWithApp(1000, 2500);
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 1000, /* dh */2500,
+ SCREEN_ORIENTATION_LANDSCAPE, /* minAspectRatio */ 0f,
+ /* horizontalReachability */ false);
+ }
+
+ @Test
+ public void testVerticalReachabilityEnabled_TranslucentLandscapeActivities_landscapeDisplay() {
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 1600, /* dh */1400,
+ SCREEN_ORIENTATION_LANDSCAPE, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ /* horizontalReachability */ false);
+ }
+
+ private void testReachabilityEnabledForTranslucentActivity(int displayWidth, int displayHeight,
+ @ScreenOrientation int screenOrientation, float minAspectRatio,
+ boolean horizontalReachability) {
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
final LetterboxConfiguration config = mWm.mLetterboxConfiguration;
config.setTranslucentLetterboxingOverrideEnabled(true);
config.setLetterboxVerticalPositionMultiplier(0.5f);
config.setIsVerticalReachabilityEnabled(true);
+ config.setLetterboxHorizontalPositionMultiplier(0.5f);
+ config.setIsHorizontalReachabilityEnabled(true);
// Opaque activity
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+ prepareMinAspectRatio(mActivity, minAspectRatio, screenOrientation);
addWindowToActivity(mActivity);
mActivity.mRootWindowContainer.performSurfacePlacement();
@@ -302,7 +263,7 @@
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
.setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
- .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setScreenOrientation(screenOrientation)
.build();
mTask.addChild(translucentActivity);
@@ -324,39 +285,78 @@
final Runnable checkIsTop = () -> assertThat(
innerBoundsOf.apply(translucentActivity).top).isEqualTo(0);
final Runnable checkIsBottom = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).bottom).isEqualTo(2500);
- final Runnable checkIsCentered = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).bottom).isEqualTo(displayHeight);
+ final Runnable checkIsLeft = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).left).isEqualTo(0);
+ final Runnable checkIsRight = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).right).isEqualTo(displayWidth);
+ final Runnable checkIsHorizontallyCentered = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).left > 0
+ && innerBoundsOf.apply(translucentActivity).right < displayWidth).isTrue();
+ final Runnable checkIsVerticallyCentered = () -> assertThat(
innerBoundsOf.apply(translucentActivity).top > 0
- && innerBoundsOf.apply(translucentActivity).bottom < 2500).isTrue();
+ && innerBoundsOf.apply(translucentActivity).bottom < displayHeight)
+ .isTrue();
- final Consumer<Integer> doubleClick =
- (Integer y) -> {
- mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
- mActivity.mRootWindowContainer.performSurfacePlacement();
- };
+ if (horizontalReachability) {
+ final Consumer<Integer> doubleClick =
+ (Integer x) -> {
+ mActivity.mLetterboxUiController.handleHorizontalDoubleTap(x);
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+ };
- // Initial state
- checkIsCentered.run();
+ // Initial state
+ checkIsHorizontallyCentered.run();
- // Double-click top
- doubleClick.accept(/* y */ 10);
- checkLetterboxPositions.run();
- checkIsTop.run();
+ // Double-click left
+ doubleClick.accept(/* x */ 10);
+ checkLetterboxPositions.run();
+ checkIsLeft.run();
- // Double-click bottom
- doubleClick.accept(/* y */ 1990);
- checkLetterboxPositions.run();
- checkIsCentered.run();
+ // Double-click right
+ doubleClick.accept(/* x */ displayWidth - 100);
+ checkLetterboxPositions.run();
+ checkIsHorizontallyCentered.run();
- // Double-click bottom
- doubleClick.accept(/* y */ 1990);
- checkLetterboxPositions.run();
- checkIsBottom.run();
+ // Double-click right
+ doubleClick.accept(/* x */ displayWidth - 100);
+ checkLetterboxPositions.run();
+ checkIsRight.run();
- // Double-click top
- doubleClick.accept(/* y */ 10);
- checkLetterboxPositions.run();
- checkIsCentered.run();
+ // Double-click left
+ doubleClick.accept(/* x */ 10);
+ checkLetterboxPositions.run();
+ checkIsHorizontallyCentered.run();
+ } else {
+ final Consumer<Integer> doubleClick =
+ (Integer y) -> {
+ mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+ };
+
+ // Initial state
+ checkIsVerticallyCentered.run();
+
+ // Double-click top
+ doubleClick.accept(/* y */ 10);
+ checkLetterboxPositions.run();
+ checkIsTop.run();
+
+ // Double-click bottom
+ doubleClick.accept(/* y */ displayHeight - 100);
+ checkLetterboxPositions.run();
+ checkIsVerticallyCentered.run();
+
+ // Double-click bottom
+ doubleClick.accept(/* y */ displayHeight - 100);
+ checkLetterboxPositions.run();
+ checkIsBottom.run();
+
+ // Double-click top
+ doubleClick.accept(/* y */ 10);
+ checkLetterboxPositions.run();
+ checkIsVerticallyCentered.run();
+ }
}
@Test
@@ -2143,7 +2143,7 @@
final Rect afterBounds = mActivity.getBounds();
final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width();
assertEquals(LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW,
- actualAspectRatio, 0.001f);
+ actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertTrue(mActivity.areBoundsLetterboxed());
}
@@ -2179,7 +2179,7 @@
// default letterbox aspect ratio for multi-window.
final Rect afterBounds = mActivity.getBounds();
final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width();
- assertEquals(minAspectRatio, actualAspectRatio, 0.001f);
+ assertEquals(minAspectRatio, actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertTrue(mActivity.areBoundsLetterboxed());
}
@@ -2487,7 +2487,7 @@
final float afterAspectRatio =
(float) Math.max(width, height) / (float) Math.min(width, height);
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2512,7 +2512,7 @@
float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2537,7 +2537,7 @@
float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2563,7 +2563,7 @@
float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2589,7 +2589,7 @@
float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2629,7 +2629,7 @@
float expectedAspectRatio = 1f * screenHeight / getExpectedSplitSize(screenWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertFalse(activity.areBoundsLetterboxed());
}
@@ -2670,7 +2670,7 @@
float expectedAspectRatio = 1f * screenWidth / getExpectedSplitSize(screenHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertFalse(activity.areBoundsLetterboxed());
}
@@ -2847,9 +2847,8 @@
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2883,9 +2882,8 @@
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2910,9 +2908,8 @@
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2937,9 +2934,8 @@
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -3609,6 +3605,32 @@
}
@Test
+ public void testIsHorizontalReachabilityEnabled_portraitDisplayAndApp_true() {
+ // Portrait display
+ setUpDisplaySizeWithApp(1400, 1600);
+ mActivity.mWmService.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+ // 16:9f unresizable portrait app
+ prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_PORTRAIT);
+
+ assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsVerticalReachabilityEnabled_landscapeDisplayAndApp_true() {
+ // Landscape display
+ setUpDisplaySizeWithApp(1600, 1500);
+ mActivity.mWmService.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+ // 16:9f unresizable landscape app
+ prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ }
+
+ @Test
public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
setUpDisplaySizeWithApp(2800, 1000);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -4053,6 +4075,32 @@
}
@Test
+ public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
+ // Set up portrait close to square display
+ setUpDisplaySizeWithApp(2200, 2280);
+ final DisplayContent display = mActivity.mDisplayContent;
+ // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+ "navbar");
+ final Binder owner = new Binder();
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, 150))
+ };
+ display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ assertTrue(navbar.providesDisplayDecorInsets()
+ && display.getDisplayPolicy().updateDecorInsetsInfo());
+ display.sendNewConfiguration();
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is fullscreen even though orientation is not respected with insets, because
+ // the display still matches or is less than the activity aspect ratio
+ assertEquals(display.getBounds(), mActivity.getBounds());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
// The display's app bounds will be (0, 100, 1000, 2350)
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500)
@@ -4275,7 +4323,7 @@
.getFixedOrientationLetterboxAspectRatio(parentConfig);
float expected = mActivity.mLetterboxUiController.getSplitScreenAspectRatio();
- assertEquals(expected, actual, 0.01);
+ assertEquals(expected, actual, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -4671,13 +4719,12 @@
.windowConfiguration.getAppBounds());
// Check that aspect ratio of app bounds is equal to the min aspect ratio.
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(fixedOrientationAppBounds), delta);
+ .computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(minAspectRatioAppBounds), delta);
+ .computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(sizeCompatAppBounds), delta);
+ .computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -4859,6 +4906,12 @@
.build();
}
+ static void prepareMinAspectRatio(ActivityRecord activity, float minAspect,
+ int screenOrientation) {
+ prepareLimitedBounds(activity, -1 /* maxAspect */, minAspect, screenOrientation,
+ true /* isUnresizable */);
+ }
+
static void prepareUnresizable(ActivityRecord activity, int screenOrientation) {
prepareUnresizable(activity, -1 /* maxAspect */, screenOrientation);
}
@@ -4873,12 +4926,18 @@
prepareLimitedBounds(activity, -1 /* maxAspect */, screenOrientation, isUnresizable);
}
- /**
- * Setups {@link #mActivity} with restriction on its bounds, such as maxAspect, fixed
- * orientation, and/or whether it is resizable.
- */
static void prepareLimitedBounds(ActivityRecord activity, float maxAspect,
int screenOrientation, boolean isUnresizable) {
+ prepareLimitedBounds(activity, maxAspect, -1 /* minAspect */, screenOrientation,
+ isUnresizable);
+ }
+
+ /**
+ * Setups {@link #mActivity} with restriction on its bounds, such as maxAspect, minAspect,
+ * fixed orientation, and/or whether it is resizable.
+ */
+ static void prepareLimitedBounds(ActivityRecord activity, float maxAspect, float minAspect,
+ int screenOrientation, boolean isUnresizable) {
activity.info.resizeMode = isUnresizable
? RESIZE_MODE_UNRESIZEABLE
: RESIZE_MODE_RESIZEABLE;
@@ -4892,6 +4951,9 @@
if (maxAspect >= 0) {
activity.info.setMaxAspectRatio(maxAspect);
}
+ if (minAspect >= 0) {
+ activity.info.setMinAspectRatio(minAspect);
+ }
if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
activity.info.screenOrientation = screenOrientation;
activity.setRequestedOrientation(screenOrientation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index db08eab..bfc13d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -61,10 +61,7 @@
assertNotNull(mWm.mTaskPositioningController);
mTarget = mWm.mTaskPositioningController;
- when(mWm.mInputManager.transferTouchFocus(
- any(InputChannel.class),
- any(InputChannel.class),
- any(boolean.class))).thenReturn(true);
+ when(mWm.mInputManager.transferTouchGesture(any(), any())).thenReturn(true);
mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4da519c..c972e51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -240,6 +240,22 @@
}
@Test
+ public void testTrackOverlayWindow() {
+ final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
+ "pkgName", "processName", 1000 /* pid */, Process.SYSTEM_UID);
+ final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
+ spyOn(session);
+ assertTrue(session.mCanAddInternalSystemWindow);
+ final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
+ session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
+ LayoutParams.TYPE_PHONE);
+ verify(session).setHasOverlayUi(true);
+ session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
+ LayoutParams.TYPE_PHONE);
+ verify(session).setHasOverlayUi(false);
+ }
+
+ @Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
index 7e97fa3..9b527dc 100644
--- a/tests/BootImageProfileTest/AndroidTest.xml
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for BootImageProfileTest">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
furthermore the changes in /data/local.prop don't actually seem to get picked up.
-->
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 70e4a71..443de8e 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
index 502c1b4..cb69c0e 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
index 591b2fa..1c6d1b3 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index 0137a85..c51da05 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index 37a91e1..ab23401 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Internal/src/com/android/internal/protolog/OWNERS
new file mode 100644
index 0000000..18cf2be
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/OWNERS
@@ -0,0 +1,3 @@
+# ProtoLog owners
+# Bug component: 1157642
+include platform/development:/tools/winscope/OWNERS