Merge "Create exported mode library for package android.service.notification" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index d1cd4fa..a5e695a 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -999,6 +999,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/core/api/system-current.txt b/core/api/system-current.txt
index 4d7ac78..627b703 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2193,6 +2193,139 @@
}
+package android.app.ondeviceintelligence {
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
+ ctor public Content(@NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public android.os.Bundle getData();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
+ method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
+ method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
+ method public default void onDownloadProgress(long);
+ method public default void onDownloadStarted(long);
+ field public static final int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; // 0x3
+ field public static final int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; // 0x2
+ field public static final int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; // 0x1
+ field public static final int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; // 0x4
+ field public static final int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Feature implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getFeatureParams();
+ method public int getId();
+ method @Nullable public String getModelName();
+ method @Nullable public String getName();
+ method public int getType();
+ method public int getVariant();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Feature> CREATOR;
+ }
+
+ public static final class Feature.Builder {
+ ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+ method @NonNull public android.app.ondeviceintelligence.Feature build();
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setVariant(int);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
+ ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
+ ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+ method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
+ method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
+ field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
+ field public static final int FEATURE_STATUS_DOWNLOADABLE = 1; // 0x1
+ field public static final int FEATURE_STATUS_DOWNLOADING = 2; // 0x2
+ field public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; // 0x4
+ field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
+ ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
+ ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
+ method public int describeContents();
+ method @NonNull public java.io.FileInputStream getFileInputStream();
+ method @NonNull public String getFilePartKey();
+ method @NonNull public android.os.PersistableBundle getFilePartParams();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@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.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ 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
+ }
+
+ public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+ method public int getErrorCode();
+ method @NonNull public android.os.PersistableBundle getErrorParams();
+ field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
+ }
+
+ public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+ field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
+ field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
+ field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
+ field public static final int PROCESSING_ERROR_CANCELLED = 7; // 0x7
+ field public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; // 0x5
+ field public static final int PROCESSING_ERROR_INTERNAL = 14; // 0xe
+ field public static final int PROCESSING_ERROR_IPC_ERROR = 6; // 0x6
+ field public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; // 0x8
+ field public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; // 0x4
+ field public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; // 0xc
+ field public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; // 0xb
+ field public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; // 0xa
+ field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
+ field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
+ field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
+ ctor public ProcessingSignal();
+ method public void sendSignal(@NonNull android.os.PersistableBundle);
+ method public void setOnProcessingSignalCallback(@NonNull java.util.concurrent.Executor, @Nullable android.app.ondeviceintelligence.ProcessingSignal.OnProcessingSignalCallback);
+ }
+
+ public static interface ProcessingSignal.OnProcessingSignalCallback {
+ method public void onSignalReceived(@NonNull android.os.PersistableBundle);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
+ method public void onNewContent(@NonNull T);
+ }
+
+}
+
package android.app.people {
public final class PeopleManager {
@@ -3640,6 +3773,7 @@
field public static final String NETD_SERVICE = "netd";
field @Deprecated public static final String NETWORK_SCORE_SERVICE = "network_score";
field public static final String OEM_LOCK_SERVICE = "oem_lock";
+ field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence";
field public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
@@ -12824,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 ca9fab8..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:
@@ -1877,6 +1885,8 @@
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
+Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
+ Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4ec476e..48dcbe5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1703,6 +1703,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 +1752,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 {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index c1181f5..27808cb 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1931,6 +1931,8 @@
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
+Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
+ Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 926e297..e6a2c07 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;
@@ -3762,11 +3761,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 +4548,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 +4556,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);
}
@@ -6189,13 +6176,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/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/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/Content.aidl b/core/java/android/app/ondeviceintelligence/Content.aidl
new file mode 100644
index 0000000..40f0ef9
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Content.aidl
@@ -0,0 +1,22 @@
+/**
+ * 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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable Content;
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
new file mode 100644
index 0000000..51bd156
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Content.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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents content sent to and received from the on-device inference service.
+ * Can contain a collection of text, image, and binary parts or any combination of these.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class Content implements Parcelable {
+ //TODO: Improve javadoc after adding validation logic.
+ private static final String TAG = "Content";
+ private final Bundle mData;
+
+ /**
+ * Create a content object using a Bundle of only known types that are read-only.
+ */
+ public Content(@NonNull Bundle data) {
+ Objects.requireNonNull(data);
+ validateBundleData(data);
+ this.mData = data;
+ }
+
+ /**
+ * Returns the Content's data represented as a Bundle.
+ */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(mData);
+ }
+
+ @Override
+ public int describeContents() {
+ int mask = 0;
+ mask |= mData.describeContents();
+ return mask;
+ }
+
+ @NonNull
+ public static final Creator<Content> CREATOR = new Creator<>() {
+ @Override
+ @NonNull
+ public Content createFromParcel(@NonNull Parcel in) {
+ return new Content(in.readBundle(getClass().getClassLoader()));
+ }
+
+ @Override
+ @NonNull
+ public Content[] newArray(int size) {
+ return new Content[size];
+ }
+ };
+
+ private void validateBundleData(Bundle unused) {
+ // TODO: Validate there are only known types.
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
new file mode 100644
index 0000000..684c71f
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.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.SystemApi;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback functions used for feature downloading via the
+ * {@link OnDeviceIntelligenceManager#requestFeatureDownload}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface DownloadCallback {
+ int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0;
+
+ /**
+ * Sent when feature download could not succeed due to there being no available disk space on
+ * the device.
+ */
+ int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1;
+
+ /**
+ * Sent when feature download could not succeed due to a network error.
+ */
+ int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2;
+
+ /**
+ * Sent when feature download has been initiated already, hence no need to request download
+ * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check if
+ * download has been completed.
+ */
+ int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3;
+
+ /**
+ * Sent when feature download did not start due to errors (e.g. remote exception of features not
+ * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check
+ * if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}.
+ */
+ int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4;
+
+ /** @hide */
+ @IntDef(value = {
+ DOWNLOAD_FAILURE_STATUS_UNKNOWN,
+ DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE,
+ DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE,
+ DOWNLOAD_FAILURE_STATUS_DOWNLOADING,
+ DOWNLOAD_FAILURE_STATUS_UNAVAILABLE
+ }, open = true)
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DownloadFailureStatus {
+ }
+
+ /**
+ * Called when model download started properly.
+ *
+ * @param bytesToDownload the total bytes to be downloaded for this {@link Feature}
+ */
+ default void onDownloadStarted(long bytesToDownload) {
+ }
+
+ /**
+ * Called when model download failed.
+ *
+ * @param failureStatus the download failure status
+ * @param errorMessage the error message associated with the download failure
+ */
+ void onDownloadFailed(
+ @DownloadFailureStatus int failureStatus,
+ @Nullable String errorMessage,
+ @NonNull PersistableBundle errorParams);
+
+ /**
+ * Called when model download is in progress.
+ *
+ * @param totalBytesDownloaded the already downloaded bytes for this {@link Feature}
+ */
+ default void onDownloadProgress(long totalBytesDownloaded) {
+ }
+
+ /**
+ * Called when model download via MDD completed. The remote implementation can populate any
+ * associated download params like file stats etc. in this callback to inform the client.
+ *
+ * @param downloadParams params containing info about the completed download.
+ */
+ void onDownloadCompleted(@NonNull PersistableBundle downloadParams);
+}
diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/core/java/android/app/ondeviceintelligence/Feature.aidl
new file mode 100644
index 0000000..18494d7
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Feature.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable Feature;
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
new file mode 100644
index 0000000..5107354
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -0,0 +1,279 @@
+/*
+ * 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.app.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.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * Represents a typical feature associated with on-device intelligence.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class Feature implements Parcelable {
+ // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
+ private final int mId;
+ @Nullable
+ private final String mName;
+ @Nullable
+ private final String mModelName;
+ private final int mType;
+ private final int mVariant;
+ @NonNull
+ private final PersistableBundle mFeatureParams;
+
+ /* package-private */ Feature(
+ int id,
+ @Nullable String name,
+ @Nullable String modelName,
+ int type,
+ int variant,
+ @NonNull PersistableBundle featureParams) {
+ this.mId = id;
+ this.mName = name;
+ this.mModelName = modelName;
+ this.mType = type;
+ this.mVariant = variant;
+ this.mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ /** Returns the unique and immutable identifier of this feature. */
+ public int getId() {
+ return mId;
+ }
+
+ /** Returns human-readable name of this feature. */
+ public @Nullable String getName() {
+ return mName;
+ }
+
+ /** Returns base model name of this feature. */
+ public @Nullable String getModelName() {
+ return mModelName;
+ }
+
+ /** Returns type identifier of this feature. */
+ public int getType() {
+ return mType;
+ }
+
+ /** Returns variant kind for this feature. */
+ public int getVariant() {
+ return mVariant;
+ }
+
+ public @NonNull PersistableBundle getFeatureParams() {
+ return mFeatureParams;
+ }
+
+ @Override
+ public String toString() {
+ return "Feature { " +
+ "id = " + mId + ", " +
+ "name = " + mName + ", " +
+ "modelName = " + mModelName + ", " +
+ "type = " + mType + ", " +
+ "variant = " + mVariant + ", " +
+ "featureParams = " + mFeatureParams +
+ " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ Feature that = (Feature) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mId == that.mId
+ && java.util.Objects.equals(mName, that.mName)
+ && java.util.Objects.equals(mModelName, that.mModelName)
+ && mType == that.mType
+ && mVariant == that.mVariant
+ && java.util.Objects.equals(mFeatureParams, that.mFeatureParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mModelName);
+ _hash = 31 * _hash + mType;
+ _hash = 31 * _hash + mVariant;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureParams);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ byte flg = 0;
+ if (mName != null) flg |= 0x2;
+ if (mModelName != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mId);
+ if (mName != null) dest.writeString8(mName);
+ if (mModelName != null) dest.writeString8(mModelName);
+ dest.writeInt(mType);
+ dest.writeInt(mVariant);
+ dest.writeTypedObject(mFeatureParams, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ /* package-private */ Feature(@NonNull Parcel in) {
+ byte flg = in.readByte();
+ int id = in.readInt();
+ String name = (flg & 0x2) == 0 ? null : in.readString();
+ String modelName = (flg & 0x4) == 0 ? null : in.readString();
+ int type = in.readInt();
+ int variant = in.readInt();
+ PersistableBundle featureParams = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mId = id;
+ this.mName = name;
+ this.mModelName = modelName;
+ this.mType = type;
+ this.mVariant = variant;
+ this.mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<Feature> CREATOR
+ = new Parcelable.Creator<Feature>() {
+ @Override
+ public Feature[] newArray(int size) {
+ return new Feature[size];
+ }
+
+ @Override
+ public Feature createFromParcel(@NonNull Parcel in) {
+ return new Feature(in);
+ }
+ };
+
+ /**
+ * A builder for {@link Feature}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder {
+ private int mId;
+ private @Nullable String mName;
+ private @Nullable String mModelName;
+ private int mType;
+ private int mVariant;
+ private @NonNull PersistableBundle mFeatureParams;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder(
+ int id,
+ int type,
+ int variant,
+ @NonNull PersistableBundle featureParams) {
+ mId = id;
+ mType = type;
+ mVariant = variant;
+ mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ public @NonNull Builder setId(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mId = value;
+ return this;
+ }
+
+ public @NonNull Builder setName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mName = value;
+ return this;
+ }
+
+ public @NonNull Builder setModelName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mModelName = value;
+ return this;
+ }
+
+ public @NonNull Builder setType(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mType = value;
+ return this;
+ }
+
+ public @NonNull Builder setVariant(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mVariant = value;
+ return this;
+ }
+
+ public @NonNull Builder setFeatureParams(@NonNull PersistableBundle value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mFeatureParams = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Feature build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40; // Mark builder used
+
+ Feature o = new Feature(
+ mId,
+ mName,
+ mModelName,
+ mType,
+ mVariant,
+ mFeatureParams);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
new file mode 100644
index 0000000..0589bf8
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable FeatureDetails;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
new file mode 100644
index 0000000..92f3513
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.text.MessageFormat;
+
+/**
+ * Represents a status of a requested {@link Feature}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class FeatureDetails implements Parcelable {
+ @Status
+ private final int mStatus;
+ @NonNull
+ private final PersistableBundle mFeatureDetailParams;
+
+ /** Invalid or unavailable {@code AiFeature}. */
+ public static final int FEATURE_STATUS_UNAVAILABLE = 0;
+
+ /** Feature can be downloaded on request. */
+ public static final int FEATURE_STATUS_DOWNLOADABLE = 1;
+
+ /** Feature is being downloaded. */
+ public static final int FEATURE_STATUS_DOWNLOADING = 2;
+
+ /** Feature is fully downloaded and ready to use. */
+ public static final int FEATURE_STATUS_AVAILABLE = 3;
+
+ /** Underlying service is unavailable and feature status cannot be fetched. */
+ public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
+
+ @IntDef(value = {
+ FEATURE_STATUS_UNAVAILABLE,
+ FEATURE_STATUS_DOWNLOADABLE,
+ FEATURE_STATUS_DOWNLOADING,
+ FEATURE_STATUS_AVAILABLE,
+ FEATURE_STATUS_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
+
+ public FeatureDetails(
+ @Status int status,
+ @NonNull PersistableBundle featureDetailParams) {
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mStatus);
+ this.mFeatureDetailParams = featureDetailParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureDetailParams);
+ }
+
+ public FeatureDetails(
+ @Status int status) {
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mStatus);
+ this.mFeatureDetailParams = new PersistableBundle();
+ }
+
+
+ /**
+ * Returns an integer value associated with the feature status.
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+
+ /**
+ * Returns a persistable bundle contain any additional status related params.
+ */
+ public @NonNull PersistableBundle getFeatureDetailParams() {
+ return mFeatureDetailParams;
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("FeatureDetails '{' status = {0}, "
+ + "persistableBundle = {1} '}'",
+ mStatus,
+ mFeatureDetailParams);
+ }
+
+ @Override
+ public boolean equals(@android.annotation.Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FeatureDetails that = (FeatureDetails) o;
+ return mStatus == that.mStatus
+ && java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mStatus;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeTypedObject(mFeatureDetailParams, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ FeatureDetails(@NonNull android.os.Parcel in) {
+ int status = in.readInt();
+ PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mStatus);
+ this.mFeatureDetailParams = persistableBundle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureDetailParams);
+ }
+
+
+ public static final @NonNull Parcelable.Creator<FeatureDetails> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public FeatureDetails[] newArray(int size) {
+ return new FeatureDetails[size];
+ }
+
+ @Override
+ public FeatureDetails createFromParcel(@NonNull android.os.Parcel in) {
+ return new FeatureDetails(in);
+ }
+ };
+
+}
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
new file mode 100644
index 0000000..e9fb5f2
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/FilePart.java
@@ -0,0 +1,137 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import android.annotation.NonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Represents file data with an associated file descriptor sent to and received from remote
+ * processing. The interface ensures that the underlying file-descriptor is always opened in
+ * read-only mode.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+@SystemApi
+public final class FilePart implements Parcelable {
+ private final String mPartKey;
+ private final PersistableBundle mPartParams;
+ private final ParcelFileDescriptor mParcelFileDescriptor;
+
+ private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
+ @NonNull ParcelFileDescriptor parcelFileDescriptor) {
+ Objects.requireNonNull(partKey);
+ Objects.requireNonNull(partParams);
+ this.mPartKey = partKey;
+ this.mPartParams = partParams;
+ this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
+ }
+
+ /**
+ * Create a file part using a filePath and any additional params.
+ */
+ public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
+ @NonNull String filePath)
+ throws FileNotFoundException {
+ this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
+ new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
+ }
+
+ /**
+ * Create a file part using a file input stream and any additional params.
+ * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
+ * call returns.
+ */
+ public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
+ @NonNull FileInputStream fileInputStream)
+ throws IOException {
+ this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
+ }
+
+ /**
+ * Returns a FileInputStream for the associated File.
+ * Caller must close the associated stream when done reading from it.
+ *
+ * @return the FileInputStream associated with the FilePart.
+ */
+ @NonNull
+ public FileInputStream getFileInputStream() {
+ return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
+ }
+
+ /**
+ * Returns the unique key associated with the part. Each Part key added to a content object
+ * should be ensured to be unique.
+ */
+ @NonNull
+ public String getFilePartKey() {
+ return mPartKey;
+ }
+
+ /**
+ * Returns the params associated with Part.
+ */
+ @NonNull
+ public PersistableBundle getFilePartParams() {
+ return mPartParams;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(getFilePartKey());
+ dest.writePersistableBundle(getFilePartParams());
+ mParcelFileDescriptor.writeToParcel(dest, flags
+ | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
+ // copy of the Pfd is closed as soon as the Binder call succeeds.
+ }
+
+ @NonNull
+ public static final Creator<FilePart> CREATOR = new Creator<>() {
+ @Override
+ public FilePart createFromParcel(Parcel in) {
+ return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
+ in.readParcelable(
+ getClass().getClassLoader(), ParcelFileDescriptor.class));
+ }
+
+ @Override
+ public FilePart[] newArray(int size) {
+ return new FilePart[size];
+ }
+ };
+}
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
new file mode 100644
index 0000000..aba563f
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for Download callback to passed onto service implementation,
+ *
+ * @hide
+ */
+oneway interface IDownloadCallback {
+ void onDownloadStarted(long bytesToDownload) = 1;
+ void onDownloadProgress(long bytesDownloaded) = 2;
+ void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDownloadCompleted(in PersistableBundle downloadParams) = 4;
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
new file mode 100644
index 0000000..93a84ec
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Feature;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving a feature for the given identifier.
+ *
+ * @hide
+ */
+interface IFeatureCallback {
+ void onSuccess(in Feature result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
new file mode 100644
index 0000000..d950290
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving details about a given feature. .
+ *
+ * @hide
+ */
+interface IFeatureDetailsCallback {
+ void onSuccess(in FeatureDetails result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
new file mode 100644
index 0000000..374cb71
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
@@ -0,0 +1,15 @@
+package android.app.ondeviceintelligence;
+
+import java.util.List;
+import android.app.ondeviceintelligence.Feature;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving list of supported features.
+ *
+ * @hide
+ */
+interface IListFeaturesCallback {
+ void onSuccess(in List<Feature> result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
new file mode 100644
index 0000000..b925f48
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -0,0 +1,70 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+ import com.android.internal.infra.AndroidFuture;
+ import android.os.ICancellationSignal;
+ import android.os.ParcelFileDescriptor;
+ import android.os.PersistableBundle;
+ import android.os.RemoteCallback;
+ import android.app.ondeviceintelligence.Content;
+ import android.app.ondeviceintelligence.Feature;
+ import android.app.ondeviceintelligence.FeatureDetails;
+ import android.app.ondeviceintelligence.IDownloadCallback;
+ import android.app.ondeviceintelligence.IListFeaturesCallback;
+ import android.app.ondeviceintelligence.IFeatureCallback;
+ import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+ import android.app.ondeviceintelligence.IResponseCallback;
+ import android.app.ondeviceintelligence.IStreamingResponseCallback;
+ import android.app.ondeviceintelligence.IProcessingSignal;
+ import android.app.ondeviceintelligence.ITokenCountCallback;
+
+
+ /**
+ * Interface for a OnDeviceIntelligenceManager for managing OnDeviceIntelligenceService and OnDeviceSandboxedInferenceService.
+ *
+ * @hide
+ */
+ oneway interface IOnDeviceIntelligenceManager {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getVersion(in RemoteCallback remoteCallback) = 1;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getFeature(in int featureId, in IFeatureCallback remoteCallback) = 2;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void listFeatures(in IListFeaturesCallback listFeaturesCallback) = 3;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal signal,
+ in ITokenCountCallback tokenCountcallback) = 6;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in IResponseCallback responseCallback) = 7;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void processRequestStreaming(in Feature feature,
+ in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in IStreamingResponseCallback streamingCallback) = 8;
+ }
diff --git a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
new file mode 100644
index 0000000..03946ee
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+* Signal to provide to the remote implementation in context of a given request or
+* feature specific event.
+*
+* @hide
+*/
+
+oneway interface IProcessingSignal {
+ void sendSignal(in PersistableBundle actionParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
new file mode 100644
index 0000000..9848e1d
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -0,0 +1,15 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for a IResponseCallback for receiving response from on-device intelligence service.
+ *
+ * @hide
+ */
+interface IResponseCallback {
+ void onSuccess(in Content result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
new file mode 100644
index 0000000..a680574
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -0,0 +1,18 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.os.PersistableBundle;
+
+
+/**
+ * This callback is a streaming variant of {@link IResponseCallback}.
+ *
+ * @hide
+ */
+interface IStreamingResponseCallback {
+ void onNewContent(in Content result) = 1;
+ void onSuccess(in Content result) = 2;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
new file mode 100644
index 0000000..b724e03
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
@@ -0,0 +1,13 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving the token count of a request for a given features.
+ *
+ * @hide
+ */
+interface ITokenCountCallback {
+ void onSuccess(long tokenCount) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS
index 6932ba2..85e9e65 100644
--- a/core/java/android/app/ondeviceintelligence/OWNERS
+++ b/core/java/android/app/ondeviceintelligence/OWNERS
@@ -4,4 +4,3 @@
shivanker@google.com
hackz@google.com
volnov@google.com
-
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
new file mode 100644
index 0000000..4d8e0d5
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -0,0 +1,624 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.LongConsumer;
+
+/**
+ * Allows granted apps to manage on-device intelligence service configured on the device. Typical
+ * calling pattern will be to query and setup a required feature before proceeding to request
+ * processing.
+ *
+ * The contracts in this Manager class are designed to be open-ended in general, to allow
+ * interoperability. Therefore, it is recommended that implementations of this system-service
+ * expose this API to the clients via a separate sdk or library which has more defined contract.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceManager {
+ public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ private final Context mContext;
+ private final IOnDeviceIntelligenceManager mService;
+
+ /**
+ * @hide
+ */
+ public OnDeviceIntelligenceManager(Context context, IOnDeviceIntelligenceManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Asynchronously get the version of the underlying remote implementation.
+ *
+ * @param versionConsumer consumer to populate the version of remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getVersion(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull LongConsumer versionConsumer) {
+ // TODO explore modifying this method into getServicePackageDetails and return both
+ // 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)));
+ });
+ mService.getVersion(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Asynchronously get feature for a given id.
+ *
+ * @param featureId the identifier pointing to the feature.
+ * @param featureReceiver callback to populate the feature object for given identifier.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getFeature(
+ int featureId,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+ try {
+ IFeatureCallback callback =
+ new IFeatureCallback.Stub() {
+ @Override
+ public void onSuccess(Feature result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureReceiver.onError(
+ new OnDeviceIntelligenceManagerException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+ mService.getFeature(featureId, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Asynchronously get a list of features that are supported for the caller.
+ *
+ * @param featureListReceiver callback to populate the list of features.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void listFeatures(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+ try {
+ IListFeaturesCallback callback =
+ new IListFeaturesCallback.Stub() {
+ @Override
+ public void onSuccess(List<Feature> result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureListReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureListReceiver.onError(
+ new OnDeviceIntelligenceManagerException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+ mService.listFeatures(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This method should be used to fetch details about a feature which need some additional
+ * computation, that can be inefficient to return in all calls to {@link #getFeature}. Callers
+ * and implementation can utilize the {@link Feature#getFeatureParams()} to pass hint on what
+ * details are expected by the caller.
+ *
+ * @param feature the feature to check status for.
+ * @param featureDetailsReceiver callback to populate the feature details to.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getFeatureDetails(@NonNull Feature feature,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+ try {
+ IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
+
+ @Override
+ public void onSuccess(FeatureDetails result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureDetailsReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureDetailsReceiver.onError(
+ new OnDeviceIntelligenceManagerException(errorCode,
+ errorMessage, errorParams))));
+ }
+ };
+ mService.getFeatureDetails(feature, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This method handles downloading all model and config files required to process requests
+ * sent against a given feature. The caller can listen to updates on the download status via
+ * the callback.
+ *
+ * Note: If a feature was already requested for downloaded previously, the onDownloadFailed
+ * callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
+ * In such cases, clients should query the feature status via {@link #getFeatureStatus} to
+ * check
+ * on the feature's download status.
+ *
+ * @param feature feature to request download for.
+ * @param callback callback to populate updates about download status.
+ * @param cancellationSignal signal to invoke cancellation on the operation in the remote
+ * implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void requestFeatureDownload(@NonNull Feature feature,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull DownloadCallback callback) {
+ try {
+ IDownloadCallback downloadCallback = new IDownloadCallback.Stub() {
+
+ @Override
+ public void onDownloadStarted(long bytesToDownload) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadStarted(bytesToDownload)));
+ }
+
+ @Override
+ public void onDownloadProgress(long bytesDownloaded) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadProgress(bytesDownloaded)));
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadFailed(failureStatus, errorMessage,
+ errorParams)));
+ }
+
+ @Override
+ public void onDownloadCompleted(PersistableBundle downloadParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> onDownloadCompleted(downloadParams)));
+ }
+ };
+
+ ICancellationSignal transport = null;
+ if (cancellationSignal != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(transport);
+ }
+
+ mService.requestFeatureDownload(feature, transport, downloadCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * The methods computes the token-count for a given request payload using the provided Feature
+ * details.
+ *
+ * @param feature feature associated with the request.
+ * @param request request that contains the content data and associated params.
+ * @param outcomeReceiver callback to populate the token count or exception in case of
+ * failure.
+ * @param cancellationSignal signal to invoke cancellation on the operation in the remote
+ * implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Long,
+ OnDeviceIntelligenceManagerException> outcomeReceiver) {
+ try {
+ ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+ @Override
+ public void onSuccess(long tokenCount) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> outcomeReceiver.onResult(tokenCount)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> outcomeReceiver.onError(
+ new OnDeviceIntelligenceManagerProcessingException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+
+ ICancellationSignal transport = null;
+ if (cancellationSignal != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(transport);
+ }
+
+ mService.requestTokenCount(feature, request, transport, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Asynchronously Process a request based on the associated params, to populate a
+ * response in
+ * {@link OutcomeReceiver#onResult} callback or failure callback status code if there
+ * was a
+ * failure.
+ *
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and
+ * associated params.
+ * @param requestType type of request being sent for processing the content.
+ * @param responseOutcomeReceiver callback to populate the response content and
+ * associated
+ * params.
+ * @param processingSignal signal to invoke custom actions in the
+ * remote implementation.
+ * @param cancellationSignal signal to invoke cancellation or
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+
+ public void processRequest(@NonNull Feature feature, @NonNull Content request,
+ @RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Content,
+ OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+ try {
+ IResponseCallback callback = new IResponseCallback.Stub() {
+ @Override
+ public void onSuccess(Content result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> responseOutcomeReceiver.onError(
+ new OnDeviceIntelligenceManagerProcessingException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+
+ IProcessingSignal transport = null;
+ if (processingSignal != null) {
+ transport = ProcessingSignal.createTransport();
+ processingSignal.setRemote(transport);
+ }
+
+ ICancellationSignal cancellationTransport = null;
+ if (cancellationSignal != null) {
+ cancellationTransport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+
+ mService.processRequest(feature, request, requestType, cancellationTransport, transport,
+ callback);
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+ * fashion, where new content is pushed to caller in chunks via the
+ * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
+ * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
+ * populate the complete {@link Response}'s Content as part of the callback when the final
+ * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+ *
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and associated
+ * params.
+ * @param requestType type of request being sent for processing the content.
+ * @param processingSignal signal to invoke other custom actions in the
+ * remote implementation.
+ * @param cancellationSignal signal to invoke cancellation
+ * @param streamingResponseReceiver streaming callback to populate the response content and
+ * associated params.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+ @RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull StreamingResponseReceiver<Content, Content,
+ OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+ try {
+ IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
+ @Override
+ public void onNewContent(Content result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingResponseReceiver.onNewContent(result));
+ });
+ }
+
+ @Override
+ public void onSuccess(Content result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingResponseReceiver.onError(
+ new OnDeviceIntelligenceManagerProcessingException(
+ errorCode, errorMessage, errorParams)));
+ });
+ }
+ };
+
+ IProcessingSignal transport = null;
+ if (processingSignal != null) {
+ transport = ProcessingSignal.createTransport();
+ processingSignal.setRemote(transport);
+ }
+
+ ICancellationSignal cancellationTransport = null;
+ if (cancellationSignal != null) {
+ cancellationTransport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+
+ mService.processRequestStreaming(
+ feature, request, requestType, cancellationTransport, transport, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /** Request inference with provided Content and Params. */
+ public static final int REQUEST_TYPE_INFERENCE = 0;
+
+ /**
+ * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+ * are time consuming beforehand to remove overhead and allow quick processing of requests
+ * thereof.
+ */
+ public static final int REQUEST_TYPE_PREPARE = 1;
+
+ /** Request Embeddings of the passed-in Content. */
+ public static final int REQUEST_TYPE_EMBEDDINGS = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ REQUEST_TYPE_INFERENCE,
+ REQUEST_TYPE_PREPARE,
+ REQUEST_TYPE_EMBEDDINGS
+ }, open = true)
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestType {
+ }
+
+
+ /**
+ * Exception type to be populated in callbacks to the methods under
+ * {@link OnDeviceIntelligenceManager}.
+ */
+ public static class OnDeviceIntelligenceManagerException extends Exception {
+ /**
+ * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+ */
+ public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
+
+ private final int mErrorCode;
+ private final PersistableBundle errorParams;
+
+ public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.errorParams = errorParams;
+ }
+
+ public OnDeviceIntelligenceManagerException(int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ this.mErrorCode = errorCode;
+ this.errorParams = errorParams;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ @NonNull
+ public PersistableBundle getErrorParams() {
+ return errorParams;
+ }
+ }
+
+ /**
+ * Exception type to be populated in callbacks to the methods under
+ * {@link OnDeviceIntelligenceManager#processRequest} or
+ * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
+ */
+ public static class OnDeviceIntelligenceManagerProcessingException extends
+ OnDeviceIntelligenceManagerException {
+
+ public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+ /** Request passed contains bad data for e.g. format. */
+ public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+ /** Bad request for inputs. */
+ public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+ /** Whole request was classified as not safe, and no response will be generated. */
+ public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+ /** Underlying processing encountered an error and failed to compute results. */
+ public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+ /** Encountered an error while performing IPC */
+ public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+ /** Request was cancelled either by user signal or by the underlying implementation. */
+ public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+ /** Underlying processing in the remote implementation is not available. */
+ public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+ /** The service is currently busy. Callers should retry with exponential backoff. */
+ public static final int PROCESSING_ERROR_BUSY = 9;
+
+ /** Something went wrong with safety classification service. */
+ public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+ /** Response generated was classified unsafe. */
+ public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+ /** Request is too large to be processed. */
+ public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+ /** Inference suspended so that higher-priority inference can run. */
+ public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+ /** Underlying processing encountered an internal error, like a violated precondition. */
+ public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+ /**
+ * The processing was not able to be passed on to the remote implementation, as the service
+ * was unavailable.
+ */
+ public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+
+ /**
+ * Error code of failed processing request.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ PROCESSING_ERROR_UNKNOWN,
+ PROCESSING_ERROR_BAD_DATA,
+ PROCESSING_ERROR_BAD_REQUEST,
+ PROCESSING_ERROR_REQUEST_NOT_SAFE,
+ PROCESSING_ERROR_COMPUTE_ERROR,
+ PROCESSING_ERROR_IPC_ERROR,
+ PROCESSING_ERROR_CANCELLED,
+ PROCESSING_ERROR_NOT_AVAILABLE,
+ PROCESSING_ERROR_BUSY,
+ PROCESSING_ERROR_SAFETY_ERROR,
+ PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+ PROCESSING_ERROR_REQUEST_TOO_LARGE,
+ PROCESSING_ERROR_SUSPENDED,
+ PROCESSING_ERROR_INTERNAL,
+ PROCESSING_ERROR_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface ProcessingError {
+ }
+
+ public OnDeviceIntelligenceManagerProcessingException(
+ @ProcessingError int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorCode, errorMessage, errorParams);
+ }
+
+ public OnDeviceIntelligenceManagerProcessingException(
+ @ProcessingError int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ super(errorCode, errorParams);
+ }
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
new file mode 100644
index 0000000..3e543d2
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
@@ -0,0 +1,221 @@
+/*
+ * 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.app.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.SystemApi;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A signal to perform orchestration actions on the inference and optionally receive a output about
+ * the result of the signal. This is an extension of {@link android.os.CancellationSignal}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class ProcessingSignal {
+ private final Object mLock = new Object();
+
+ private static final int MAX_QUEUE_SIZE = 10;
+
+ @GuardedBy("mLock")
+ private final ArrayDeque<PersistableBundle> mActionParamsQueue;
+
+ @GuardedBy("mLock")
+ private IProcessingSignal mRemote;
+
+ private OnProcessingSignalCallback mOnProcessingSignalCallback;
+ private Executor mExecutor;
+
+ public ProcessingSignal() {
+ mActionParamsQueue = new ArrayDeque<>(MAX_QUEUE_SIZE);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when processing signals are received.
+ */
+ public interface OnProcessingSignalCallback {
+ /**
+ * Called when a custom signal was received.
+ * This method allows the receiver to provide logic to be executed based on the signal
+ * received.
+ *
+ * @param actionParams Parameters for the signal in the form of a {@link PersistableBundle}.
+ */
+
+ void onSignalReceived(@NonNull PersistableBundle actionParams);
+ }
+
+
+ /**
+ * Sends a custom signal with the provided parameters. It also signals the remote callback
+ * with the same params if already configured, if not the action is queued to be sent when a
+ * remote is configured. Similarly, on the receiver side, the callback will be invoked if
+ * already set, if not all actions are queued to be sent to callback when it is set.
+ *
+ * @param actionParams Parameters for the signal.
+ */
+ public void sendSignal(@NonNull PersistableBundle actionParams) {
+ final OnProcessingSignalCallback callback;
+ final IProcessingSignal remote;
+ synchronized (mLock) {
+ if (mActionParamsQueue.size() > MAX_QUEUE_SIZE) {
+ throw new RuntimeException(
+ "Maximum actions that can be queued are : " + MAX_QUEUE_SIZE);
+ }
+
+ mActionParamsQueue.add(actionParams);
+ callback = mOnProcessingSignalCallback;
+ remote = mRemote;
+
+ if (callback != null) {
+ while (!mActionParamsQueue.isEmpty()) {
+ PersistableBundle params = mActionParamsQueue.removeFirst();
+ mExecutor.execute(
+ () -> callback.onSignalReceived(params));
+ }
+ }
+ if (remote != null) {
+ while (!mActionParamsQueue.isEmpty()) {
+ try {
+ remote.sendSignal(mActionParamsQueue.removeFirst());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Sets the processing signal callback to be called when signals are received.
+ *
+ * This method is intended to be used by the recipient of a processing signal
+ * such as the remote implementation for {@link OnDeviceIntelligenceManager} to handle
+ * cancellation requests while performing a long-running operation. This method is not
+ * intended
+ * to be used by applications themselves.
+ *
+ * If {@link ProcessingSignal#sendSignal} has already been called, then the provided callback
+ * is invoked immediately and all previously queued actions are passed to remote signal.
+ *
+ * This method is guaranteed that the callback will not be called after it
+ * has been removed.
+ *
+ * @param callback The processing signal callback, or null to remove the current callback.
+ * @param executor Executor to the run the callback methods on.
+ */
+ public void setOnProcessingSignalCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable OnProcessingSignalCallback callback) {
+ Objects.requireNonNull(executor);
+ synchronized (mLock) {
+ if (mOnProcessingSignalCallback == callback) {
+ return;
+ }
+
+ mOnProcessingSignalCallback = callback;
+ mExecutor = executor;
+ if (callback == null || mActionParamsQueue.isEmpty()) {
+ return;
+ }
+
+ while (!mActionParamsQueue.isEmpty()) {
+ PersistableBundle params = mActionParamsQueue.removeFirst();
+ mExecutor.execute(() -> callback.onSignalReceived(params));
+ }
+ }
+ }
+
+ /**
+ * Sets the remote transport.
+ *
+ * If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also
+ * sequentially sent to the remote.
+ *
+ * This method is guaranteed that the remote transport will not be called after it
+ * has been removed.
+ *
+ * @param remote The remote transport, or null to remove.
+ * @hide
+ */
+ void setRemote(IProcessingSignal remote) {
+ synchronized (mLock) {
+ mRemote = remote;
+ if (mActionParamsQueue.isEmpty() || remote == null) {
+ return;
+ }
+
+ while (!mActionParamsQueue.isEmpty()) {
+ try {
+ remote.sendSignal(mActionParamsQueue.removeFirst());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to send action to remote signal", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a transport that can be returned back to the caller of
+ * a Binder function and subsequently used to dispatch a processing signal.
+ *
+ * @return The new processing signal transport.
+ * @hide
+ */
+ public static IProcessingSignal createTransport() {
+ return new Transport();
+ }
+
+ /**
+ * Given a locally created transport, returns its associated cancellation signal.
+ *
+ * @param transport The locally created transport, or null if none.
+ * @return The associated processing signal, or null if none.
+ * @hide
+ */
+ public static ProcessingSignal fromTransport(IProcessingSignal transport) {
+ if (transport instanceof Transport) {
+ return ((Transport) transport).mProcessingSignal;
+ }
+ return null;
+ }
+
+ private static final class Transport extends IProcessingSignal.Stub {
+ final ProcessingSignal mProcessingSignal = new ProcessingSignal();
+
+ @Override
+ public void sendSignal(PersistableBundle actionParams) {
+ mProcessingSignal.sendSignal(actionParams);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
new file mode 100644
index 0000000..ebcf61c
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+/**
+ * Streaming variant of outcome receiver to populate response while processing a given request,
+ * possibly in
+ * chunks to provide a async processing behaviour to the caller.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
+ OutcomeReceiver<R, E> {
+ /**
+ * Callback to be invoked when a part of the response i.e. some {@link Content} is already
+ * processed and
+ * needs to be passed onto the caller.
+ */
+ void onNewContent(@NonNull T content);
+}
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/Context.java b/core/java/android/content/Context.java
index 58cea0d..e6e7fa8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6451,6 +6451,19 @@
@SystemApi
public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}.
+ *
+ * @see #getSystemService(String)
+ * @see OnDeviceIntelligenceManager
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+ public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence";
+
/**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.health.connect.HealthConnectManager}.
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/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/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/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/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ed43b81..d463b62 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -376,4 +376,10 @@
* @param packageName of the session for which the output switcher is shown.
*/
void showMediaOutputSwitcher(String packageName);
+
+ /** Enters desktop mode.
+ *
+ * @param displayId the id of the current display.
+ */
+ void enterDesktop(int displayId);
}
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/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/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/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/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/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/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index e732a03..8305fa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -47,4 +47,8 @@
default void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
Executor callbackExecutor) { }
+
+ /** Called when requested to go to desktop mode from the current focused app. */
+ void enterDesktop(int displayId);
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 11304ec..6de5d74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -21,6 +21,7 @@
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
@@ -240,6 +241,43 @@
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
}
+ /** Enter desktop by using the focused task in given `displayId` */
+ fun enterDesktop(displayId: Int) {
+ val allFocusedTasks =
+ shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
+ taskInfo.isFocused &&
+ (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ taskInfo.activityType != ACTIVITY_TYPE_HOME
+ }
+ if (allFocusedTasks.isNotEmpty()) {
+ when (allFocusedTasks.size) {
+ 2 -> {
+ // Split-screen case where there are two focused tasks, then we find the child
+ // task to move to desktop.
+ val splitFocusedTask = findChildFocusedTask(allFocusedTasks)
+ moveToDesktop(splitFocusedTask)
+ }
+ 1 -> {
+ // Fullscreen case where we move the current focused task.
+ moveToDesktop(allFocusedTasks[0].taskId)
+ }
+ else -> {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: Cannot enter desktop expected less " +
+ "than 3 focused tasks but found " + allFocusedTasks.size
+ )
+ }
+ }
+ }
+ }
+
+ private fun findChildFocusedTask(allFocusedTasks: List<RunningTaskInfo>): RunningTaskInfo {
+ if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) return allFocusedTasks[1]
+ return allFocusedTasks[0]
+ }
+
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(
taskId: Int,
@@ -1012,6 +1050,12 @@
this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
}
}
+
+ override fun enterDesktop(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterDesktop(displayId)
+ }
+ }
}
/** The interface for calls from outside the host process. */
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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 63618f4..cb64c52 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -734,6 +734,46 @@
shellExecutor.flushAll()
verify(launchAdjacentController).launchAdjacentEnabled = true
}
+ @Test
+ fun enterDesktop_fullscreenTaskIsMovedToDesktop() {
+ val task1 = setUpFullscreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+
+ controller.enterDesktop(DEFAULT_DISPLAY)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun enterDesktop_splitScreenTaskIsMovedToDesktop() {
+ val task1 = setUpSplitScreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+ val task4 = setUpSplitScreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+ task4.isFocused = true
+
+ task4.parentTaskId = task1.taskId
+
+ controller.enterDesktop(DEFAULT_DISPLAY)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
+ )
+ }
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
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/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index ec519ca..463e9be 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha01"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha02"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index f6fbc02..fe378c2 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -16,7 +16,7 @@
[versions]
agp = "8.2.2"
-compose-compiler = "1.5.8"
+compose-compiler = "1.5.9"
dexmaker-mockito = "2.28.3"
jvm = "17"
kotlin = "1.9.22"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 08a8797..2259bd74 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-rc01")
+ api("androidx.compose.material3:material3:1.2.0")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha01")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha02")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1092a16..9588e50 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1129,7 +1129,7 @@
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
<string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
<!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
- <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
+ <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
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/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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7f229fb..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."
@@ -469,3 +479,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "register_zen_mode_content_observer_background"
+ namespace: "systemui"
+ description: "Decide whether to register zen mode content observers in the background thread."
+ bug: "324515627"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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/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/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index c8fbad4..76e7c95 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -348,6 +348,8 @@
// Compute the destination scene (and therefore offset) to settle in.
val offset = swipeTransition.dragOffset
val distance = swipeTransition.distance
+ var targetScene: Scene
+ var targetOffset: Float
if (
shouldCommitSwipe(
offset,
@@ -356,12 +358,24 @@
wasCommitted = swipeTransition._currentScene == toScene,
)
) {
- // Animate to the next scene
- animateTo(targetScene = toScene, targetOffset = distance)
+ targetScene = toScene
+ targetOffset = distance
} else {
- // Animate to the initial scene
- animateTo(targetScene = fromScene, targetOffset = 0f)
+ targetScene = fromScene
+ targetOffset = 0f
}
+
+ if (
+ targetScene != swipeTransition._currentScene &&
+ !layoutState.canChangeScene(targetScene.key)
+ ) {
+ // We wanted to change to a new scene but we are not allowed to, so we animate back
+ // to the current scene.
+ targetScene = swipeTransition._currentScene
+ targetOffset = if (targetScene == fromScene) 0f else distance
+ }
+
+ animateTo(targetScene = targetScene, targetOffset = targetOffset)
} else {
// We are doing an overscroll animation between scenes. In this case, we can also start
// from the idle position.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 8c5a472..08399ff 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -232,7 +232,12 @@
scene(state.transitionState.currentScene).userActions[Back]?.let { result ->
// TODO(b/290184746): Handle predictive back and use result.distance if
// specified.
- BackHandler { with(state) { coroutineScope.onChangeScene(result.toScene) } }
+ BackHandler {
+ val targetScene = result.toScene
+ if (state.canChangeScene(targetScene)) {
+ with(state) { coroutineScope.onChangeScene(targetScene) }
+ }
+ }
}
Box {
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 a8da551..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
@@ -101,13 +101,30 @@
): TransitionState.Transition?
}
-/** Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene]. */
+/**
+ * Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene].
+ *
+ * @param initialScene the initial scene to which this state is initialized.
+ * @param transitions the [SceneTransitions] used when this state is transitioning between scenes.
+ * @param canChangeScene whether we can transition to the given scene. This is called when the user
+ * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
+ * `true`, then the gesture will be committed and we will animate to the other scene. Otherwise,
+ * the gesture will be cancelled and we will animate back to the current scene.
+ * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
+ * [SceneTransitionLayoutState]s.
+ */
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
+ canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
): MutableSceneTransitionLayoutState {
- return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
+ return MutableSceneTransitionLayoutStateImpl(
+ initialScene,
+ transitions,
+ canChangeScene,
+ stateLinks,
+ )
}
/**
@@ -120,18 +137,32 @@
* This is called when the user commits a transition to a new scene because of a [UserAction], for
* instance by triggering back navigation or by swiping to a new scene.
* @param transitions the definition of the transitions used to animate a change of scene.
+ * @param canChangeScene whether we can transition to the given scene. This is called when the user
+ * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
+ * `true`, then [onChangeScene] will be called right afterwards with the same [SceneKey]. If it
+ * returns `false`, the user action will be cancelled and we will animate back to the current
+ * scene.
+ * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
+ * [SceneTransitionLayoutState]s.
*/
@Composable
fun updateSceneTransitionLayoutState(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions = SceneTransitions.Empty,
+ canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
): SceneTransitionLayoutState {
return remember {
- HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+ HoistedSceneTransitionLayoutState(
+ currentScene,
+ transitions,
+ onChangeScene,
+ canChangeScene,
+ stateLinks,
+ )
}
- .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
+ .apply { update(currentScene, onChangeScene, canChangeScene, transitions, stateLinks) }
}
@Stable
@@ -208,6 +239,9 @@
private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+ /** Whether we can transition to the given [scene]. */
+ internal abstract fun canChangeScene(scene: SceneKey): Boolean
+
/**
* Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
*
@@ -330,25 +364,30 @@
* 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,
+ private var canChangeScene: (SceneKey) -> Boolean,
stateLinks: List<StateLink> = emptyList(),
) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
- override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
+ override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
+
+ override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene.invoke(scene)
@Composable
fun update(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
+ canChangeScene: (SceneKey) -> Boolean,
transitions: SceneTransitions,
stateLinks: List<StateLink>,
) {
SideEffect {
this.changeScene = onChangeScene
+ this.canChangeScene = canChangeScene
this.transitions = transitions
this.stateLinks = stateLinks
@@ -361,7 +400,7 @@
// late.
val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
animateToScene(
- layoutState = this@HoistedSceneTransitionLayoutScene,
+ layoutState = this@HoistedSceneTransitionLayoutState,
target = newKey,
transitionKey = null,
)
@@ -374,6 +413,7 @@
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
override var transitions: SceneTransitions,
+ private val canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
override fun setTargetScene(
@@ -388,6 +428,8 @@
)
}
+ override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
+
override fun CoroutineScope.onChangeScene(scene: SceneKey) {
setTargetScene(scene, coroutineScope = this)
}
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/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index c91d298..fe53d5b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -51,8 +51,13 @@
private class TestGestureScope(
private val testScope: MonotonicClockTestScope,
) {
+ var canChangeScene: (SceneKey) -> Boolean = { true }
private val layoutState =
- MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ MutableSceneTransitionLayoutStateImpl(
+ SceneA,
+ EmptyTestTransitions,
+ canChangeScene = { canChangeScene(it) },
+ )
val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
@@ -890,4 +895,41 @@
)
assertThat(transitionState).isNotSameInstanceAs(firstTransition)
}
+
+ @Test
+ fun blockTransition() = runGestureTest {
+ assertIdle(SceneA)
+
+ // Swipe up to scene B.
+ onDragStarted(overSlop = up(0.1f))
+ assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
+
+ // Block the transition when the user release their finger.
+ canChangeScene = { false }
+ onDragStopped(velocity = -velocityThreshold)
+ advanceUntilIdle()
+ assertIdle(SceneA)
+ }
+
+ @Test
+ fun blockInterceptedTransition() = runGestureTest {
+ assertIdle(SceneA)
+
+ // Swipe up to B.
+ onDragStarted(overSlop = up(0.1f))
+ assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
+ onDragStopped(velocity = -velocityThreshold)
+ assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
+
+ // Intercept the transition and swipe down back to scene A.
+ assertThat(sceneGestureHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ onDragStartedImmediately()
+
+ // Block the transition when the user release their finger.
+ canChangeScene = { false }
+ onDragStopped(velocity = velocityThreshold)
+
+ advanceUntilIdle()
+ assertIdle(SceneB)
+ }
}
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/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 4c824c0..87d25dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -46,12 +46,12 @@
import android.graphics.Region;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
@@ -75,8 +75,8 @@
import org.mockito.junit.MockitoRule;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4.class)
public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
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/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/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ffb11dd..ca19f71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -177,6 +177,7 @@
private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT;
private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
+ private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -520,6 +521,11 @@
* @see IStatusBar#immersiveModeChanged
*/
default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {}
+
+ /**
+ * @see IStatusBar#enterDesktop(int)
+ */
+ default void enterDesktop(int displayId) {}
}
@VisibleForTesting
@@ -1420,6 +1426,13 @@
mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
}
+ @Override
+ public void enterDesktop(int displayId) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = displayId;
+ mHandler.obtainMessage(MSG_ENTER_DESKTOP, args).sendToTarget();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1914,6 +1927,13 @@
mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
}
break;
+ case MSG_ENTER_DESKTOP:
+ args = (SomeArgs) msg.obj;
+ int displayId = args.argi1;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).enterDesktop(displayId);
+ }
+ break;
}
}
}
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/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 1414150..2c1780d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -158,7 +158,15 @@
@Override
public void showNotification(@NonNull NotificationEntry entry) {
mLogger.logShowNotification(entry);
- addEntry(entry);
+
+ // Add new entry and begin managing it
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
entry.setInterruption();
}
@@ -319,19 +327,6 @@
}
/**
- * Add a new entry and begin managing it.
- * @param entry the entry to add
- */
- protected final void addEntry(@NonNull NotificationEntry entry) {
- HeadsUpEntry headsUpEntry = createHeadsUpEntry();
- headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- }
-
- /**
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b0..600005b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static com.android.systemui.Flags.registerZenModeContentObserverBackground;
+
import android.app.AlarmManager;
import android.app.Flags;
import android.app.NotificationManager;
@@ -45,6 +47,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
@@ -104,6 +107,7 @@
public ZenModeControllerImpl(
Context context,
@Main Handler handler,
+ @Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher,
DumpManager dumpManager,
GlobalSettings globalSettings,
@@ -134,9 +138,18 @@
}
};
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ if (registerZenModeContentObserverBackground()) {
+ bgHandler.post(() -> {
+ globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+ configContentObserver);
+ });
+ } else {
+ globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+ configContentObserver);
+ }
updateZenMode(getModeSettingValueFromProvider());
- globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver);
updateZenModeConfig();
updateConsolidatedNotificationPolicy();
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e832506..15e0965 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -356,6 +356,12 @@
// TODO(b/278084491): update sysui state for changes on other displays
}
}, mSysUiMainExecutor);
+ mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+ @Override
+ public void enterDesktop(int displayId) {
+ desktopMode.enterDesktop(displayId);
+ }
+ });
}
@VisibleForTesting
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/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/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index f1a2c28..ddd29c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -79,6 +79,7 @@
mController = new ZenModeControllerImpl(
mContext,
Handler.createAsync(TestableLooper.get(this).getLooper()),
+ Handler.createAsync(TestableLooper.get(this).getLooper()),
mBroadcastDispatcher,
mDumpManager,
mGlobalSettings,
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/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/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/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/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 6a6e6ab..c2c82ed 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.grammaticalinflection;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
@@ -55,11 +56,11 @@
*
*/
public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
- Configuration configuration);
+ @NonNull Configuration configuration);
/**
* Whether the package can get the system grammatical gender or not.
*/
- public abstract boolean canGetSystemGrammaticalGender(int uid, String packageName);
+ public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 258e97d..0bcb26d 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -355,12 +355,11 @@
final File file = getGrammaticalGenderFile(userId);
synchronized (mLock) {
if (!file.exists()) {
- Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+ Log.d(TAG, "User " + userId + " doesn't have the grammatical gender file.");
return;
}
if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
- try {
- InputStream in = new FileInputStream(file);
+ try (FileInputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
} catch (IOException | XmlPullParserException e) {
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..5574d18 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(
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/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/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/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a4c6959..3c6baa8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -255,4 +255,9 @@
* @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
*/
void removeQsTile(ComponentName tile);
+
+ /**
+ * Called when requested to enter desktop from an app.
+ */
+ void enterDesktop(int displayId);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fd316ea..14c38bd 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -830,6 +830,15 @@
}
@Override
+ public void enterDesktop(int displayId) {
+ IStatusBar bar = mBar;
+ if (bar != null) {
+ try {
+ bar.enterDesktop(displayId);
+ } catch (RemoteException ex) { }
+ }
+ }
+ @Override
public void showMediaOutputSwitcher(String packageName) {
IStatusBar bar = mBar;
if (bar != null) {
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/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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c36df8d..2696fb6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1846,20 +1846,20 @@
mLetterboxUiController.onMovedToDisplay(mDisplayContent.getDisplayId());
}
- void layoutLetterbox(WindowState winHint) {
- mLetterboxUiController.layoutLetterbox(winHint);
+ void layoutLetterboxIfNeeded(WindowState winHint) {
+ mLetterboxUiController.layoutLetterboxIfNeeded(winHint);
}
boolean hasWallpaperBackgroundForLetterbox() {
return mLetterboxUiController.hasWallpaperBackgroundForLetterbox();
}
- void updateLetterboxSurface(WindowState winHint, Transaction t) {
- mLetterboxUiController.updateLetterboxSurface(winHint, t);
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) {
+ mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t);
}
- void updateLetterboxSurface(WindowState winHint) {
- mLetterboxUiController.updateLetterboxSurface(winHint);
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint) {
+ mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint);
}
/** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
@@ -4546,7 +4546,7 @@
}
super.removeChild(child);
checkKeyguardFlagsChanged();
- updateLetterboxSurface(child);
+ updateLetterboxSurfaceIfNeeded(child);
}
void setAppLayoutChanges(int changes, String reason) {
@@ -6036,7 +6036,7 @@
if (destroyedSomething) {
final DisplayContent dc = getDisplayContent();
dc.assignWindowLayers(true /*setLayoutNeeded*/);
- updateLetterboxSurface(null);
+ updateLetterboxSurfaceIfNeeded(null);
}
}
@@ -7688,7 +7688,7 @@
}
if (mNeedsLetterboxedAnimation) {
- updateLetterboxSurface(findMainWindow(), t);
+ updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
mNeedsAnimationBoundsLayer = true;
}
@@ -7856,7 +7856,7 @@
mNeedsAnimationBoundsLayer = false;
if (mNeedsLetterboxedAnimation) {
mNeedsLetterboxedAnimation = false;
- updateLetterboxSurface(findMainWindow(), t);
+ updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
}
if (mAnimatingActivityRegistry != null) {
@@ -8943,6 +8943,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/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 2c49203..533529a 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -639,6 +639,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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3637ab1..adbe800 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5554,7 +5554,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 +5562,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..38ee456 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -629,7 +629,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 +773,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 +998,7 @@
}
final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[2];
targets[0] = mCloseAdaptor.mAnimationTarget;
- targets[1] = mOpenAnimAdaptor.getOrCreateAnimationTarget();
+ targets[1] = mOpenAnimAdaptor.mRemoteAnimationTarget;
return targets;
}
@@ -1067,11 +1072,12 @@
}
}
- 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 +1146,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 +1175,151 @@
}
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(@NonNull WindowContainer closeWindow,
+ 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);
+ 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.
+ mRemoteAnimationTarget.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 (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) {
+ transaction.reparent(mStartingSurface,
+ mAdaptors[0].mTarget.getSurfaceControl());
+ } else {
+ // 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 +1331,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 +1362,6 @@
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mCapturedLeash == animationLeash) {
mCapturedLeash = null;
- mRequestedStartingSurfaceId = INVALID_TASK_ID;
- mStartingSurface = null;
}
}
@@ -1345,84 +1429,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(
@@ -1499,18 +1505,10 @@
* @param visibleOpenActivities The visible activities in opening targets.
*/
private void applyPreviewStrategy(@NonNull WindowContainer closeWindow,
- @NonNull BackWindowAnimationAdaptor[] openAnimationAdaptor,
+ @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(closeWindow, 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(mCloseTarget, 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/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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e743172..d3acd71 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1127,7 +1127,7 @@
final ActivityRecord activity = w.mActivityRecord;
if (activity != null && activity.isVisibleRequested()) {
- activity.updateLetterboxSurface(w);
+ activity.updateLetterboxSurfaceIfNeeded(w);
final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
mTmpUpdateAllDrawn.add(activity);
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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index edf9da1..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;
@@ -924,21 +923,21 @@
return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
}
- void updateLetterboxSurface(WindowState winHint) {
- updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction());
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint) {
+ updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction());
}
- void updateLetterboxSurface(WindowState winHint, Transaction t) {
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) {
if (shouldNotLayoutLetterbox(winHint)) {
return;
}
- layoutLetterbox(winHint);
+ layoutLetterboxIfNeeded(winHint);
if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
mLetterbox.applySurfaceChanges(t);
}
}
- void layoutLetterbox(WindowState w) {
+ void layoutLetterboxIfNeeded(WindowState w) {
if (shouldNotLayoutLetterbox(w)) {
return;
}
@@ -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/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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e538f5d5..61480d2 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();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6acf1f3..ee16a37 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -28,7 +28,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ProcessList.INVALID_ADJ;
-import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -299,7 +298,7 @@
*/
private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
- private boolean mCanUseSystemGrammaticalGender;
+ private final boolean mCanUseSystemGrammaticalGender;
public WindowProcessController(@NonNull ActivityTaskManagerService atm,
@NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6e993b3..b8f1d15 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1332,7 +1332,7 @@
updateSourceFrame(windowFrames.mFrame);
if (mActivityRecord != null && !mIsChildWindow) {
- mActivityRecord.layoutLetterbox(this);
+ mActivityRecord.layoutLetterboxIfNeeded(this);
}
mSurfacePlacementNeeded = true;
mHaveFrame = true;
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..d5d26be 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.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/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/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/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/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/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/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index 6a15b05..f1d84cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -40,8 +40,10 @@
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.wm.utils.CommonUtils;
import com.android.window.flags.Flags;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -77,6 +79,11 @@
});
}
+ @After
+ public void tearDown() {
+ CommonUtils.waitUntilActivityRemoved(mActivity);
+ }
+
@RequiresFlagsDisabled(Flags.FLAG_SURFACE_TRUSTED_OVERLAY)
@Test
public void setTrustedOverlayInputWindow() throws InterruptedException {
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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d99abe8..5d99acd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4803,12 +4803,51 @@
*/
public static final String KEY_FCM_SENDER_ID_STRING = KEY_PREFIX + "fcm_sender_id_string";
+ /**
+ * Indicates the supported protocol version in the parameter entitlement_version.
+ * The default value is 2. The possible value is 2 and 8.
+ *
+ * Reference: GSMA TS.43-v8 section 2.5 Protocol version control and
+ * Table 3. GET Parameters for Entitlement Configuration in section 2.3
+ * HTTP GET method Parameters.
+ * @hide
+ */
+ public static final String KEY_ENTITLEMENT_VERSION_INT =
+ KEY_PREFIX + "entitlement_version_int";
+
+ /**
+ * Controls the service entitlement status when receiving the VERS characteristic
+ * with both version and validity set to -1 or -2.
+ * If {@code true}, default service entitlement status is enabled.
+ * If {@code false}, default service entitlement status is disabled.
+ *
+ * Reference: GSMA TS.14-v8 section 2.1, overview
+ * @hide
+ */
+ public static final String KEY_DEFAULT_SERVICE_ENTITLEMENT_STATUS_BOOL =
+ KEY_PREFIX + "default_service_entitlement_status_bool";
+
+ /**
+ * Indicates if UE can skip service entitlement check when the user turns on Wi-Fi Calling.
+ * UE still shows Wi-Fi Calling emergency address update web view when the user clicks
+ * "Update Emergency Address" on the WiFi calling setting.
+ *
+ * Note: this is effective only if the {@link #KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING}
+ * is set to this app.
+ * @hide
+ */
+ public static final String KEY_SKIP_WFC_ACTIVATION_BOOL =
+ KEY_PREFIX + "skip_wfc_activation_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putString(KEY_ENTITLEMENT_SERVER_URL_STRING, "");
defaults.putString(KEY_FCM_SENDER_ID_STRING, "");
defaults.putBoolean(KEY_SHOW_VOWIFI_WEBVIEW_BOOL, false);
defaults.putBoolean(KEY_IMS_PROVISIONING_BOOL, false);
+ defaults.putBoolean(KEY_DEFAULT_SERVICE_ENTITLEMENT_STATUS_BOOL, false);
+ defaults.putBoolean(KEY_SKIP_WFC_ACTIVATION_BOOL, false);
+ defaults.putInt(KEY_ENTITLEMENT_VERSION_INT, 2);
return defaults;
}
}
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